Class: Glimmer::SWT::ModelBinding

Inherits:
Object
  • Object
show all
Includes:
Observable, Observer
Defined in:
lib/glimmer/swt/model_binding.rb

Constant Summary collapse

@@property_type_converters =
{
  :undefined => lambda { |value| value },
  :fixnum => lambda { |value| value.to_i },
  :array => lambda { |value| value.to_a }
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Observer

#add_dependent, #dependents, #dependents_for, proc, #register, #registrations, #registrations_for, #remove_dependent, #unregister, #unregister_all_observables, #unregister_dependents_with_observable

Constructor Details

#initialize(base_model, property_name_expression, property_type = :undefined, binding_options = nil) ⇒ ModelBinding

Returns a new instance of ModelBinding.



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/glimmer/swt/model_binding.rb', line 16

def initialize(base_model, property_name_expression, property_type = :undefined, binding_options = nil)
  property_type = :undefined if property_type.nil?
  @base_model = base_model
  @property_name_expression = property_name_expression
  @property_type = property_type
  @binding_options = binding_options || {}
  if computed?
    @computed_model_bindings = computed_by.map do |computed_by_property_expression|
      self.class.new(base_model, computed_by_property_expression, :undefined, computed_binding_options)
    end
  end
end

Instance Attribute Details

#binding_optionsObject (readonly)

Returns the value of attribute binding_options.



10
11
12
# File 'lib/glimmer/swt/model_binding.rb', line 10

def binding_options
  @binding_options
end

#property_typeObject (readonly)

Returns the value of attribute property_type.



10
11
12
# File 'lib/glimmer/swt/model_binding.rb', line 10

def property_type
  @property_type
end

Instance Method Details

#add_computed_observers(observer) ⇒ Object



141
142
143
144
145
146
# File 'lib/glimmer/swt/model_binding.rb', line 141

def add_computed_observers(observer)
  @computed_model_bindings.each do |computed_model_binding|
    computed_observer_for(observer).observe(computed_model_binding)
    observer.add_dependent([self, nil] => [computed_observer_for(observer), computed_model_binding, nil])
  end
end

#add_nested_observers(observer) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/glimmer/swt/model_binding.rb', line 147

def add_nested_observers(observer)
  nested_property_observers = nested_property_observers_for(observer)
  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
    parent_model = previous_index.negative? ? self : nested_models[previous_index]
    parent_property_name = previous_index.negative? ? nil : nested_property_names[previous_index]
    parent_observer = previous_index.negative? ? observer : nested_property_observers[parent_property_name]
    parent_property_name = nil if parent_property_name.to_s.start_with?('[')
    unless model.nil?
      if property_indexed?(property_name)
        # TODO figure out a way to deal with this more uniformly
        nested_property_observer.observe(model)
        parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, nil])
      else
        nested_property_observer.observe(model, property_name)
        parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, property_name])
      end
    end
  end
end

#add_observer(observer) ⇒ Object



110
111
112
113
114
115
116
117
118
119
# File 'lib/glimmer/swt/model_binding.rb', line 110

def add_observer(observer)
  if computed?
    add_computed_observers(observer)
  elsif nested_property?
    add_nested_observers(observer)
  else
    observer.observe(model, property_name)
    observer.add_dependent([self, nil] => [observer, model, property_name])
  end
end

#base_modelObject



48
49
50
# File 'lib/glimmer/swt/model_binding.rb', line 48

def base_model
  @base_model
end

#call(value) ⇒ Object



169
170
171
172
173
# File 'lib/glimmer/swt/model_binding.rb', line 169

def call(value)
  return if model.nil?
  converted_value = @@property_type_converters[@property_type].call(value)
  invoke_property_writer(model, "#{property_name}=", converted_value) unless evaluate_property == converted_value
end

#computed?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/glimmer/swt/model_binding.rb', line 77

def computed?
  !computed_by.empty?
end

#computed_binding_optionsObject



83
84
85
# File 'lib/glimmer/swt/model_binding.rb', line 83

def computed_binding_options
  @binding_options.reject {|k,v| k == :computed_by}
end

#computed_byObject



80
81
82
# File 'lib/glimmer/swt/model_binding.rb', line 80

def computed_by
  [@binding_options[:computed_by]].flatten.compact
end

#computed_observer_for(observer) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/glimmer/swt/model_binding.rb', line 132

def computed_observer_for(observer)
  @computed_observer_collection ||= {}
  unless @computed_observer_collection.has_key?(observer)
    @computed_observer_collection[observer] = Observer.proc do |new_value|
      observer.call(evaluate_property)
    end
  end
  @computed_observer_collection[observer]
end

#evaluate_options_propertyObject



177
178
179
# File 'lib/glimmer/swt/model_binding.rb', line 177

def evaluate_options_property
  model.send(options_property_name) unless model.nil?
end

#evaluate_propertyObject



174
175
176
# File 'lib/glimmer/swt/model_binding.rb', line 174

def evaluate_property
  invoke_property_reader(model, property_name) unless model.nil?
end

#invoke_property_reader(object, property_expression) ⇒ Object



186
187
188
189
190
191
192
193
194
195
# File 'lib/glimmer/swt/model_binding.rb', line 186

def invoke_property_reader(object, property_expression)
  if property_indexed?(property_expression)
    property_method = '[]'
    property_argument = property_expression[1...-1]
    property_argument = property_argument.to_i if property_argument.match(/\d+/)
    object.send(property_method, property_argument)
  else
    object.send(property_expression)
  end
end

#invoke_property_writer(object, property_expression, value) ⇒ Object



196
197
198
199
200
201
202
203
204
205
# File 'lib/glimmer/swt/model_binding.rb', line 196

def invoke_property_writer(object, property_expression, value)
  if property_indexed?(property_expression)
    property_method = '[]='
    property_argument = property_expression[1...-2]
    property_argument = property_argument.to_i if property_argument.match(/\d+/)
    object.send(property_method, property_argument, value)
  else
    object.send(property_expression, value)
  end
end

#modelObject



28
29
30
# File 'lib/glimmer/swt/model_binding.rb', line 28

def model
  nested_property? ? nested_model : base_model
end

#model_property_namesObject

Model representing nested property names e.g. property name expression “address.state” gives [:address]



68
69
70
# File 'lib/glimmer/swt/model_binding.rb', line 68

def model_property_names
  nested_property_names[0...-1]
end

#nested_modelObject



45
46
47
# File 'lib/glimmer/swt/model_binding.rb', line 45

def nested_model
  nested_models.last
end

#nested_modelsObject

e.g. person.address.state returns [person, person.address]



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/glimmer/swt/model_binding.rb', line 32

def nested_models
  @nested_models = [base_model]
  model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name|
    if reduced_model.nil?
      nil
    else
      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

#nested_property?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/glimmer/swt/model_binding.rb', line 71

def nested_property?
  property_name_expression.match(/[.\[]/)
end

#nested_property_nameObject

Final nested property name e.g. property name expression “address.state” gives :state



63
64
65
# File 'lib/glimmer/swt/model_binding.rb', line 63

def nested_property_name
  nested_property_names.last
end

#nested_property_namesObject

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’]



58
59
60
# File 'lib/glimmer/swt/model_binding.rb', line 58

def nested_property_names
  @nested_property_names ||= property_name_expression.split(".").map {|pne| pne.match(/([^\[]+)(\[[^\]]+\])?/).to_a.drop(1)}.flatten.compact
end

#nested_property_observers_for(observer) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/glimmer/swt/model_binding.rb', line 86

def nested_property_observers_for(observer)
  @nested_property_observers_collection ||= {}
  unless @nested_property_observers_collection.has_key?(observer)
    @nested_property_observers_collection[observer] = nested_property_names.reduce({}) 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)
          observer.call(evaluate_property)
        end
      )
    end
  end
  # @nested_property_observers_collection[observer].keys.each_with_index do |property_name, i|
  #   previous_property_name = nested_property_names[i-1]
  #   previous_observer = @nested_property_observers_collection[observer][previous_property_name]
  #   nested_property_observer = @nested_property_observers_collection[observer][property_name]
  #   previous_observer.add_dependent(nested_property_observer) unless previous_observer.nil?
  # end
  # TODO remove this brainstorming
  # person.addresses[1].streets[2].number
  # person.addresses[1] = ...
  @nested_property_observers_collection[observer]
end

#options_property_nameObject



180
181
182
# File 'lib/glimmer/swt/model_binding.rb', line 180

def options_property_name
  self.property_name + "_options"
end

#property_indexed?(property_expression) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/glimmer/swt/model_binding.rb', line 183

def property_indexed?(property_expression)
  property_expression.start_with?('[')
end

#property_nameObject



51
52
53
# File 'lib/glimmer/swt/model_binding.rb', line 51

def property_name
  nested_property? ? nested_property_name : property_name_expression
end

#property_name_expressionObject



74
75
76
# File 'lib/glimmer/swt/model_binding.rb', line 74

def property_name_expression
  @property_name_expression
end

#remove_observer(observer) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/glimmer/swt/model_binding.rb', line 120

def remove_observer(observer)
  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?
    nested_property_observers_for(observer).clear
  else
    observer.unobserve(model, property_name)
  end
end