class Glimmer::DataBinding::ModelBinding
Constants
- ARRAY_INDEXED_PROPERTY_ARGUMENT_REGEX
- HASH_DOUBLE_QUOTE_INDEXED_PROPERTY_ARGUMENT_REGEX
- HASH_SINGLE_QUOTE_INDEXED_PROPERTY_ARGUMENT_REGEX
- HASH_SYMBOL_INDEXED_PROPERTY_ARGUMENT_REGEX
Attributes
Public Class Methods
Source
# File lib/glimmer/data_binding/model_binding.rb, line 38 def initialize(*args) binding_options = args.pop if args.size > 1 && args.last.is_a?(Hash) @base_model, @property_name_expression = args @binding_options = binding_options || Concurrent::Hash.new if computed? @computed_model_bindings = Concurrent::Array.new(computed_by.map do |computed_by_property_expression| self.class.new(base_model, computed_by_property_expression) end) end end
Public Instance Methods
Source
# File lib/glimmer/data_binding/model_binding.rb, line 180 def add_computed_observers(observer) @computed_model_bindings.each do |computed_model_binding| observer_registration = computed_observer_for(observer).observe(computed_model_binding, observation_options) my_registration = observer.registration_for(self) observer.add_dependent(my_registration => observer_registration) end end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 188 def add_nested_observers(observer) nested_property_observers = nested_property_observers_for(observer) Concurrent::Array.new(nested_models.zip(nested_property_names)).each_with_index do |zip, i| model, property_name = zip nested_property_observer = nested_property_observers[property_name] previous_index = i - 1 if previous_index.negative? parent_model = self parent_property_name = nil parent_observer = observer else parent_model = nested_models[previous_index] parent_property_name = nested_property_names[previous_index] parent_observer = nested_property_observers[parent_property_name] end parent_property_name = nil if parent_property_name.to_s.start_with?('[') unless model.nil? # TODO figure out a way to deal with this more uniformly observer_registration = property_indexed?(property_name) ? nested_property_observer.observe(model, observation_options) : nested_property_observer.observe(model, property_name, observation_options) parent_registration = parent_observer.registration_for(parent_model, *[parent_property_name].compact) parent_observer.add_dependent(parent_registration => observer_registration) end end end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 133 def add_observer(observer, extra_options = {}) # TODO couldn't we have a scenario where it is both computed? and nested_property? at the same time? # or computed and not nested at the same time (else statement)? if computed? add_computed_observers(observer) elsif nested_property? add_nested_observers(observer) else model_binding_observer = Observer.proc do |new_value| converted_value = evaluate_property observer.call(converted_value).tap do apply_processor(@binding_options[:after_read], converted_value) end end observer_registration = model_binding_observer.observe(*([model] + [property_name, observation_options].compact)) my_registration = observer.registration_for(self) observer.add_dependent(my_registration => observer_registration) end end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 70 def base_model @base_model end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 213 def call(value, *extra_args) return if model.nil? converted_value = value invoke_property_writer(model, model.is_a?(Hash) && !property_indexed?(property_name) ? property_name : "#{property_name}=", converted_value) unless converted_value == evaluate_property || property_name.nil? end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 102 def computed? !computed_by.empty? end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 106 def computed_by Concurrent::Array.new([@binding_options[:computed_by]].flatten.compact) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 167 def computed_observer_for(observer) @computed_observer_collection ||= Concurrent::Hash.new unless @computed_observer_collection.has_key?(observer) @computed_observer_collection[observer] = Observer.proc do |new_value| converted_value = evaluate_property observer.call(converted_value).tap do apply_processor(@binding_options[:after_read], converted_value) end end end @computed_observer_collection[observer] end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 226 def evaluate_options_property model.send(options_property_name) unless model.nil? end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 219 def evaluate_property value = nil value = invoke_property_reader(model, property_name) unless model.nil? apply_processor(@binding_options[:before_read], value) convert_on_read(value) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 49 def model nested_property? ? nested_model : base_model end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 94 def model_property_names Concurrent::Array.new(nested_property_names[0...-1]) end
Model representing nested property names e.g. property name expression “address.state” gives [‘address’]
Source
# File lib/glimmer/data_binding/model_binding.rb, line 66 def nested_model nested_models.last end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 54 def nested_models @nested_models = Concurrent::Array.new([base_model]) model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name| if !reduced_model.nil? invoke_property_reader(reduced_model, nested_model_property_name).tap do |new_reduced_model| @nested_models << new_reduced_model end end end @nested_models end
e.g. person.address.state returns [person, person.address]
Source
# File lib/glimmer/data_binding/model_binding.rb, line 98 def nested_property? property_name_expression.to_s.match(/[.\[]/) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 88 def nested_property_name nested_property_names.last end
Final nested property name e.g. property name expression “address.state” gives :state
Source
# File lib/glimmer/data_binding/model_binding.rb, line 82 def nested_property_names @nested_property_names ||= Concurrent::Array.new(property_name_expression.split(/\[|\./).map {|pne| pne.end_with?(']') ? "[#{pne}" : pne }.reject {|pne| pne.empty? }) end
All nested property names e.g. property name expression “address.state” gives [‘address’, ‘state’] If there are any indexed property names, this returns indexes as properties. e.g. property name expression “addresses.state” gives [‘addresses’, ‘[1]’, ‘state’]
Source
# File lib/glimmer/data_binding/model_binding.rb, line 114 def nested_property_observers_for(observer) @nested_property_observers_collection ||= Concurrent::Hash.new unless @nested_property_observers_collection.has_key?(observer) @nested_property_observers_collection[observer] = nested_property_names.reduce(Concurrent::Hash.new) do |output, property_name| output.merge( property_name => Observer.proc do |new_value| # Ensure reattaching observers when a higher level nested property is updated (e.g. person.address changes reattaches person.address.street observer) add_observer(observer) converted_value = evaluate_property observer.call(converted_value).tap do apply_processor(@binding_options[:after_read], converted_value) end end ) end end @nested_property_observers_collection[observer] end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 110 def observation_options @binding_options.slice(:recursive) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 230 def options_property_name "#{self.property_name}_options" end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 234 def property_indexed?(property_expression) property_expression.to_s.start_with?('[') end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 74 def property_name nested_property? ? nested_property_name : property_name_expression end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 153 def remove_observer(observer, extra_options = {}) if computed? @computed_model_bindings.each do |computed_model_binding| computed_observer_for(observer).unobserve(computed_model_binding) end @computed_observer_collection.delete(observer) elsif nested_property? # No need to call remove_nested_observers(observer) (cleanup happens automatically indirectly when invoked through observer.unobserve(model_binding)) nested_property_observers_for(observer).clear else observer.unobserve(model, property_name) end end
Private Instance Methods
Source
# File lib/glimmer/data_binding/model_binding.rb, line 248 def apply_processor(processor, value) return value if processor.nil? return value.send(processor) if (processor.is_a?(String) || processor.is_a?(Symbol)) && value.respond_to?(processor) return invoke_proc_with_exact_parameters(processor, value) if processor.respond_to?(:call) raise Glimmer::Error, "Unsupported bind processor: #{processor.inspect}" end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 240 def convert_on_read(value) apply_processor(@binding_options[:on_read], value) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 244 def convert_on_write(value) apply_processor(@binding_options[:on_write], value) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 298 def indexed_property_argument(property_argument) if property_argument.match(ARRAY_INDEXED_PROPERTY_ARGUMENT_REGEX) property_argument.to_i elsif property_argument.match(HASH_SYMBOL_INDEXED_PROPERTY_ARGUMENT_REGEX) property_argument.sub(':', '').to_sym elsif property_argument.match(HASH_SINGLE_QUOTE_INDEXED_PROPERTY_ARGUMENT_REGEX) property_argument.gsub("'", '') elsif property_argument.match(HASH_DOUBLE_QUOTE_INDEXED_PROPERTY_ARGUMENT_REGEX) property_argument.gsub('"', '') else property_argument end end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 255 def invoke_proc_with_exact_parameters(proc_object, *args) return if proc_object.nil? args = Concurrent::Array.new(args[0...proc_object.parameters.size]) proc_object.call(*args) end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 261 def invoke_property_reader(object, property_expression) if property_indexed?(property_expression) property_method = '[]' property_argument = property_expression[1...-1] property_argument = indexed_property_argument(property_argument) object.send(property_method, property_argument) else if property_expression.nil? object elsif object.is_a?(Hash) object[property_expression] else object.send(property_expression) end end end
Source
# File lib/glimmer/data_binding/model_binding.rb, line 278 def invoke_property_writer(object, property_expression, value) return if property_expression.nil? raise "Cannot invoke `#{property_expression}` because ModelBinding#binding_options[:read_only]=true" if @binding_options[:read_only] apply_processor(@binding_options[:before_write], value) converted_value = convert_on_write(value) if property_indexed?(property_expression) property_method = '[]=' property_argument = property_expression[1...-2] property_argument = indexed_property_argument(property_argument) object.send(property_method, property_argument, converted_value) else if object.is_a?(Hash) object[property_expression] = converted_value else object.send(property_expression, converted_value) end end apply_processor(@binding_options[:after_write], converted_value) end