Module: Glimmer::DataBinding::ObservableModel

Includes:
ObservableHashable
Defined in:
lib/glimmer/data_binding/observable_model.rb

Defined Under Namespace

Classes: Notifier

Constant Summary collapse

PROPERTY_WRITER_FACTORY =
lambda do |property_name, property_writer_name, options|
  lambda do |value|
    old_value = self.send(property_name)
    unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer
    self.send("__original__#{property_writer_name}", value)
    notify_observers(property_name)
    ensure_array_object_observer(property_name, value, old_value, options)
  end
end

Constants included from ObservableHashable

Glimmer::DataBinding::ObservableHashable::OBSERVED_STORE_METHOD

Instance Method Summary collapse

Methods included from ObservableHashable

#add_key_writer_observer, #store_method

Methods included from Observable

#inspect

Instance Method Details

#add_observer(observer, property_name, options = {}) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/glimmer/data_binding/observable_model.rb', line 53

def add_observer(observer, property_name, options = {})
  initialize_observer_options(options)
  return observer if has_observer?(observer, property_name)
  property_observer_list(property_name) << observer
  # if property writer does not exist, add_property_writer_observers will ensure_array_object_observer only
  add_property_writer_observers(property_name, options)
  add_key_writer_observer(property_name, options) unless (['Struct', 'OpenStruct'] & self.class.ancestors.map(&:to_s)).empty?
  observer
end

#add_property_writer_observers(property_name, options) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/glimmer/data_binding/observable_model.rb', line 115

def add_property_writer_observers(property_name, options)
  options[:attribute_writer_type].each do |attribute_writer_type|
    begin
      property_writer_name = attribute_writer_type.to_s.gsub('attribute', property_name.to_s)
      ensure_array_object_observer(property_name, send(property_name), nil, options)
      method(property_writer_name)
      begin
        method("__original__#{property_writer_name}")
      rescue
        define_singleton_method("__original__#{property_writer_name}", property_writer_method(property_writer_name))
        # Note the limitation that the first observe call options apply to all subsequent observations meaning even if unobserve was called, options do not change from initial ones
        # It is good enough for now. If there is a need to address this in the future, this is where to start the work
        define_singleton_method(property_writer_name, &PROPERTY_WRITER_FACTORY.call(property_name, property_writer_name, options))
      end
    rescue => e
      #ignore writing if no property writer exists
      Glimmer::Config.logger.debug {"No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"}
    end
  end
end

#all_property_observer_listObject Also known as: all_key_observer_list



106
107
108
# File 'lib/glimmer/data_binding/observable_model.rb', line 106

def all_property_observer_list
  property_observer_list(nil)
end

#array_object_observer_for(property_name) ⇒ Object



159
160
161
162
163
# File 'lib/glimmer/data_binding/observable_model.rb', line 159

def array_object_observer_for(property_name)
  @array_object_observers ||= Concurrent::Hash.new
  @array_object_observers[property_name] = Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
  @array_object_observers[property_name]
end

#ensure_array_object_observer(property_name, object, old_object = nil, options = nil) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
# File 'lib/glimmer/data_binding/observable_model.rb', line 147

def ensure_array_object_observer(property_name, object, old_object = nil, options = nil)
  options ||= {}
  return unless object&.is_a?(Array)
  array_object_observer = array_object_observer_for(property_name)
  array_observer_registration = array_object_observer.observe(object, options)
  property_observer_list(property_name).each do |observer|
    my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
    observer.add_dependent(my_registration => array_observer_registration)
  end
  array_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableArray)
end

#has_observer?(observer, property_name) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/glimmer/data_binding/observable_model.rb', line 87

def has_observer?(observer, property_name)
  property_observer_list(property_name).include?(observer)
end

#has_observer_for_any_property?(observer) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/glimmer/data_binding/observable_model.rb', line 91

def has_observer_for_any_property?(observer)
  property_observer_hash.values.map(&:to_a).reduce(:+).include?(observer)
end

#initialize_observer_options(options) ⇒ Object



165
166
167
168
# File 'lib/glimmer/data_binding/observable_model.rb', line 165

def initialize_observer_options(options)
  options[:attribute_writer_type] ||= [:attribute=]
  options[:attribute_writer_type] = [options[:attribute_writer_type]] if !options[:attribute_writer_type].is_a?(Array)
end

#notify_observers(property_name) ⇒ Object



111
112
113
# File 'lib/glimmer/data_binding/observable_model.rb', line 111

def notify_observers(property_name)
  property_observer_list(property_name).to_a.each { |observer| observer.call(send(property_name)) }
end

#property_observer_hashObject



95
96
97
# File 'lib/glimmer/data_binding/observable_model.rb', line 95

def property_observer_hash
  @property_observers ||= Concurrent::Hash.new
end

#property_observer_list(property_name) ⇒ Object Also known as: key_observer_list



99
100
101
102
103
# File 'lib/glimmer/data_binding/observable_model.rb', line 99

def property_observer_list(property_name)
  property_key = property_name&.to_sym
  property_observer_hash[property_key] = Concurrent::Set.new unless property_observer_hash[property_key]
  property_observer_hash[property_key]
end

#property_writer_method(property_writer_name) ⇒ Object



136
137
138
# File 'lib/glimmer/data_binding/observable_model.rb', line 136

def property_writer_method(property_writer_name)
  self.class.instance_method(property_writer_name) rescue self.method(property_writer_name)
end

#remove_all_observersObject



78
79
80
81
82
83
84
85
# File 'lib/glimmer/data_binding/observable_model.rb', line 78

def remove_all_observers
  all_observers = property_observer_hash.clone
  property_observer_hash.keys.each do |property_name|
    remove_observers(property_name)
  end
  property_observer_hash.clear
  all_observers
end

#remove_observer(observer, property_name, options = {}) ⇒ Object



63
64
65
66
67
68
# File 'lib/glimmer/data_binding/observable_model.rb', line 63

def remove_observer(observer, property_name, options = {})
  if has_observer?(observer, property_name)
    property_observer_list(property_name).delete(observer)
    observer.unobserve(self, property_name)
  end
end

#remove_observers(property_name) ⇒ Object



70
71
72
73
74
75
76
# File 'lib/glimmer/data_binding/observable_model.rb', line 70

def remove_observers(property_name)
  property_key = property_name&.to_sym
  property_observer_hash[property_key].each do |observer|
    remove_observer(observer, property_name)
  end
  property_observer_hash.delete(property_key)
end

#unregister_dependent_observers(property_name, old_value) ⇒ Object Also known as: deregister_dependent_observers



140
141
142
143
144
# File 'lib/glimmer/data_binding/observable_model.rb', line 140

def unregister_dependent_observers(property_name, old_value)
  # TODO look into optimizing this
  return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
  property_observer_list(property_name).each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self, property_name), old_value) }
end