volt 0.8.27.beta3 → 0.8.27.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/{Readme.md → README.md} +9 -12
  6. data/Rakefile +2 -9
  7. data/VERSION +1 -1
  8. data/app/volt/models/user.rb +8 -0
  9. data/app/volt/tasks/live_query/data_store.rb +13 -5
  10. data/app/volt/tasks/live_query/live_query.rb +45 -3
  11. data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
  12. data/app/volt/tasks/query_tasks.rb +20 -2
  13. data/app/volt/tasks/store_tasks.rb +37 -20
  14. data/app/volt/tasks/user_tasks.rb +15 -13
  15. data/lib/volt/boot.rb +5 -3
  16. data/lib/volt/cli/console.rb +1 -0
  17. data/lib/volt/cli/generate.rb +15 -0
  18. data/lib/volt/cli.rb +19 -12
  19. data/lib/volt/config.rb +1 -1
  20. data/lib/volt/controllers/model_controller.rb +13 -3
  21. data/lib/volt/extra_core/extra_core.rb +1 -0
  22. data/lib/volt/extra_core/hash.rb +26 -0
  23. data/lib/volt/extra_core/object.rb +5 -1
  24. data/lib/volt/models/array_model.rb +86 -35
  25. data/lib/volt/models/associations.rb +53 -0
  26. data/lib/volt/models/buffer.rb +22 -10
  27. data/lib/volt/models/dirty.rb +88 -0
  28. data/lib/volt/models/errors.rb +21 -0
  29. data/lib/volt/models/field_helpers.rb +2 -2
  30. data/lib/volt/models/listener_tracker.rb +17 -0
  31. data/lib/volt/models/model.rb +213 -69
  32. data/lib/volt/models/model_helpers.rb +27 -17
  33. data/lib/volt/models/permissions.rb +246 -0
  34. data/lib/volt/models/persistors/array_store.rb +149 -81
  35. data/lib/volt/models/persistors/base.rb +16 -0
  36. data/lib/volt/models/persistors/cookies.rb +14 -9
  37. data/lib/volt/models/persistors/flash.rb +3 -0
  38. data/lib/volt/models/persistors/local_store.rb +0 -16
  39. data/lib/volt/models/persistors/model_store.rb +1 -2
  40. data/lib/volt/models/persistors/query/normalizer.rb +51 -0
  41. data/lib/volt/models/persistors/query/query_listener.rb +21 -5
  42. data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
  43. data/lib/volt/models/persistors/store.rb +8 -0
  44. data/lib/volt/models/persistors/store_state.rb +4 -27
  45. data/lib/volt/models/state_helpers.rb +11 -0
  46. data/lib/volt/models/state_manager.rb +43 -0
  47. data/lib/volt/models/url.rb +5 -5
  48. data/lib/volt/models/validations.rb +38 -41
  49. data/lib/volt/models/validators/email_validator.rb +4 -9
  50. data/lib/volt/models/validators/format_validator.rb +23 -8
  51. data/lib/volt/models/validators/length_validator.rb +2 -2
  52. data/lib/volt/models/validators/numericality_validator.rb +7 -3
  53. data/lib/volt/models/validators/phone_number_validator.rb +4 -9
  54. data/lib/volt/models/validators/presence_validator.rb +2 -2
  55. data/lib/volt/models/validators/unique_validator.rb +2 -2
  56. data/lib/volt/models/validators/user_validation.rb +6 -0
  57. data/lib/volt/models.rb +8 -3
  58. data/lib/volt/page/bindings/attribute_binding.rb +10 -4
  59. data/lib/volt/page/bindings/content_binding.rb +9 -5
  60. data/lib/volt/page/bindings/if_binding.rb +25 -2
  61. data/lib/volt/page/bindings/template_binding.rb +19 -1
  62. data/lib/volt/page/bindings/yield_binding.rb +31 -0
  63. data/lib/volt/page/page.rb +11 -16
  64. data/lib/volt/reactive/class_eventable.rb +71 -0
  65. data/lib/volt/reactive/computation.rb +79 -10
  66. data/lib/volt/reactive/dependency.rb +27 -8
  67. data/lib/volt/reactive/eventable.rb +36 -22
  68. data/lib/volt/reactive/reactive_array.rb +2 -3
  69. data/lib/volt/reactive/reactive_hash.rb +8 -3
  70. data/lib/volt/router/routes.rb +2 -1
  71. data/lib/volt/server/component_templates.rb +0 -2
  72. data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
  73. data/lib/volt/server/html_parser/view_handler.rb +3 -0
  74. data/lib/volt/server/html_parser/view_parser.rb +1 -0
  75. data/lib/volt/server/html_parser/view_scope.rb +17 -41
  76. data/lib/volt/server/rack/component_paths.rb +1 -10
  77. data/lib/volt/server/rack/index_files.rb +9 -4
  78. data/lib/volt/server/rack/opal_files.rb +22 -14
  79. data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
  80. data/lib/volt/server/socket_connection_handler.rb +4 -0
  81. data/lib/volt/spec/setup.rb +26 -0
  82. data/lib/volt/tasks/dispatcher.rb +11 -0
  83. data/lib/volt/utils/event_counter.rb +29 -0
  84. data/lib/volt/utils/generic_pool.rb +12 -0
  85. data/lib/volt/utils/modes.rb +40 -0
  86. data/lib/volt/utils/promise_patch.rb +66 -0
  87. data/lib/volt/utils/timers.rb +33 -0
  88. data/lib/volt/volt/users.rb +48 -5
  89. data/lib/volt.rb +4 -0
  90. data/spec/apps/kitchen_sink/Gemfile +3 -1
  91. data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
  92. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
  93. data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
  94. data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
  95. data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
  96. data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
  97. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
  98. data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
  99. data/spec/extra_core/logger_spec.rb +4 -2
  100. data/spec/integration/user_spec.rb +42 -42
  101. data/spec/integration/yield_spec.rb +18 -0
  102. data/spec/models/associations_spec.rb +37 -0
  103. data/spec/models/dirty_spec.rb +102 -0
  104. data/spec/models/model_spec.rb +64 -8
  105. data/spec/models/model_state_spec.rb +24 -0
  106. data/spec/models/permissions_spec.rb +96 -0
  107. data/spec/models/user_spec.rb +8 -5
  108. data/spec/models/user_validation_spec.rb +24 -0
  109. data/spec/models/validations_spec.rb +44 -5
  110. data/spec/models/validators/email_validator_spec.rb +109 -82
  111. data/spec/models/validators/format_validator_spec.rb +4 -107
  112. data/spec/models/validators/length_validator_spec.rb +9 -9
  113. data/spec/models/validators/phone_number_validator_spec.rb +60 -103
  114. data/spec/models/validators/shared_examples_for_validators.rb +123 -0
  115. data/spec/reactive/class_eventable_spec.rb +37 -0
  116. data/spec/reactive/computation_spec.rb +68 -3
  117. data/spec/reactive/dependency_spec.rb +71 -0
  118. data/spec/reactive/eventable_spec.rb +21 -0
  119. data/spec/reactive/reactive_hash_spec.rb +12 -0
  120. data/spec/router/routes_spec.rb +50 -50
  121. data/spec/server/html_parser/view_parser_spec.rb +0 -3
  122. data/spec/server/rack/component_paths_spec.rb +11 -0
  123. data/spec/server/rack/quite_common_logger_spec.rb +3 -4
  124. data/spec/spec_helper.rb +7 -3
  125. data/templates/component/config/dependencies.rb +1 -7
  126. data/templates/component/config/routes.rb +1 -1
  127. data/templates/component/controllers/main_controller.rb.tt +20 -0
  128. data/templates/component/views/{index → main}/index.html.tt +0 -0
  129. data/templates/newgem/lib/newgem.rb.tt +1 -3
  130. data/templates/project/app/main/config/routes.rb +3 -3
  131. data/templates/project/app/main/views/main/main.html.tt +4 -4
  132. data/templates/project/config/app.rb.tt +6 -0
  133. data/volt.gemspec +11 -7
  134. metadata +96 -42
  135. data/lib/volt/models/model_state.rb +0 -21
  136. data/templates/component/controllers/main_controller.rb +0 -18
@@ -30,7 +30,11 @@ class Object
30
30
 
31
31
  # TODO: Need a real implementation of this
32
32
  def deep_clone
33
- Marshal.load(Marshal.dump(self))
33
+ if RUBY_PLATFORM == 'opal'
34
+ JSON.parse(self.to_json)
35
+ else
36
+ Marshal.load(Marshal.dump(self))
37
+ end
34
38
  end
35
39
 
36
40
  def try(*a, &b)
@@ -1,22 +1,27 @@
1
1
  require 'volt/reactive/reactive_array'
2
2
  require 'volt/models/model_wrapper'
3
3
  require 'volt/models/model_helpers'
4
- require 'volt/models/model_state'
4
+ require 'volt/models/state_manager'
5
+ require 'volt/models/state_helpers'
5
6
 
6
7
  module Volt
7
8
  class ArrayModel < ReactiveArray
8
9
  include ModelWrapper
9
10
  include ModelHelpers
10
- include ModelState
11
+ include StateManager
12
+ include StateHelpers
13
+
11
14
 
12
15
  attr_reader :parent, :path, :persistor, :options, :array
13
16
 
14
17
  # For many methods, we want to call load data as soon as the model is interacted
15
18
  # with, so we proxy the method, then call super.
16
- def self.proxy_with_load_data(*method_names)
19
+ def self.proxy_with_root_dep(*method_names)
17
20
  method_names.each do |method_name|
18
21
  define_method(method_name) do |*args|
19
- load_data
22
+ # track on the root dep
23
+ persistor.try(:root_dep).try(:depend)
24
+
20
25
  super(*args)
21
26
  end
22
27
  end
@@ -35,8 +40,8 @@ module Volt
35
40
  end
36
41
  end
37
42
 
38
- proxy_with_load_data :[], :size, :first, :last
39
- proxy_to_persistor :find, :skip, :limit, :then
43
+ proxy_with_root_dep :[], :size, :first, :last, :state_for#, :limit, :find_one, :find
44
+ proxy_to_persistor :find, :where, :skip, :sort, :limit, :then, :fetch, :fetch_first
40
45
 
41
46
  def initialize(array = [], options = {})
42
47
  @options = options
@@ -48,7 +53,11 @@ module Volt
48
53
 
49
54
  super(array)
50
55
 
51
- @persistor.loaded if @persistor
56
+ if @persistor
57
+ @persistor.loaded
58
+ else
59
+ change_state_to(:loaded_state, :loaded, false)
60
+ end
52
61
  end
53
62
 
54
63
  def attributes
@@ -57,8 +66,6 @@ module Volt
57
66
 
58
67
  # Make sure it gets wrapped
59
68
  def <<(model)
60
- load_data
61
-
62
69
  if model.is_a?(Model)
63
70
  # Set the new path
64
71
  model.options = @options.merge(path: @options[:path] + [:[]])
@@ -66,23 +73,48 @@ module Volt
66
73
  model = wrap_values([model]).first
67
74
  end
68
75
 
76
+ if model.is_a?(Model) && !model.can_create?
77
+ raise "permissions did not allow create for #{model.inspect}"
78
+ end
79
+
69
80
  super(model)
70
81
 
71
82
  if @persistor
72
- @persistor.added(model, @array.size - 1)
83
+ promise = @persistor.added(model, @array.size - 1)
84
+ if promise && promise.is_a?(Promise)
85
+ return promise.fail do |err|
86
+ # remove from the collection because it failed to save on the server
87
+ @array.delete(model)
88
+
89
+ # TODO: the model might be in at a different position already, so we should use a full delete
90
+ trigger_removed!(@array.size - 1)
91
+ trigger_size_change!
92
+ #
93
+ # re-raise, err might not be an Error object, so we use a rejected promise to re-raise
94
+ Promise.new.reject(err)
95
+ end
96
+ end
73
97
  else
74
98
  nil
75
99
  end
76
100
  end
77
101
 
78
- # Works like << except it returns a promise
102
+ # Works like << except it always returns a promise
79
103
  def append(model)
80
- promise, model = send(:<<, model)
81
-
82
- # Return a promise if one doesn't exist
83
- promise ||= Promise.new.resolve(model)
104
+ # Wrap results in a promise
105
+ Promise.new.resolve(nil).then do
106
+ send(:<<, model)
107
+ end
108
+ end
84
109
 
85
- promise
110
+ def delete(val)
111
+ # Check to make sure the models are allowed to be deleted
112
+ if !val.is_a?(Model) || val.can_delete?
113
+ result = super
114
+ Promise.new.resolve(result)
115
+ else
116
+ Promise.new.reject("permissions did not allow delete for #{val.inspect}.")
117
+ end
86
118
  end
87
119
 
88
120
  # Find one does a query, but only returns the first item or
@@ -92,6 +124,32 @@ module Volt
92
124
  find(*args, &block).limit(1)[0]
93
125
  end
94
126
 
127
+ def first
128
+ self[0]
129
+ end
130
+
131
+ # returns a promise to fetch the first instance
132
+ def fetch_first(&block)
133
+ persistor = self.persistor
134
+
135
+ if persistor && persistor.is_a?(Persistors::ArrayStore)
136
+ # On array store, we wait for the result to be loaded in.
137
+ promise = limit(1).fetch do |res|
138
+ result = res.first
139
+
140
+ next result
141
+ end
142
+ else
143
+ # On all other persistors, it should be loaded already
144
+ promise = Promise.new.resolve(first)
145
+ end
146
+
147
+ # Run any passed in blocks after fetch
148
+ promise = promise.then(&block) if block
149
+
150
+ promise
151
+ end
152
+
95
153
  # Make sure it gets wrapped
96
154
  def inject(*args)
97
155
  args = wrap_values(args)
@@ -105,7 +163,7 @@ module Volt
105
163
  end
106
164
 
107
165
  def new_model(*args)
108
- class_at_path(options[:path]).new(*args)
166
+ Volt::Model.class_at_path(options[:path]).new(*args)
109
167
  end
110
168
 
111
169
  def new_array_model(*args)
@@ -122,23 +180,23 @@ module Volt
122
180
  end
123
181
 
124
182
  def inspect
125
- # Just load the data on the server making it easier to work with
126
- load_data if Volt.server?
127
-
128
- if @persistor && @persistor.is_a?(Persistors::ArrayStore) && state == :not_loaded
129
- # Show a special message letting users know it is not loaded yet.
130
- "#<#{self.class}:not loaded, access with [] or size to load>"
131
- else
132
- # Otherwise inspect normally
133
- super
183
+ Computation.run_without_tracking do
184
+ # Track on size
185
+ @size_dep.depend
186
+ str = "#<#{self.class}:#{object_id} #{loaded_state}"
187
+ str += " path:#{path.join('.')}" if path
188
+ # str += " persistor:#{persistor.inspect}" if persistor
189
+ str += " #{@array.inspect}>"
190
+
191
+ str
134
192
  end
135
193
  end
136
194
 
137
195
  def buffer
138
196
  model_path = options[:path] + [:[]]
139
- model_klass = class_at_path(model_path)
197
+ model_klass = Volt::Model.class_at_path(model_path)
140
198
 
141
- new_options = options.merge(path: model_path, save_to: self).reject { |k, _| k.to_sym == :persistor }
199
+ new_options = options.merge(path: model_path, save_to: self, buffer: true).reject { |k, _| k.to_sym == :persistor }
142
200
  model = model_klass.new({}, new_options)
143
201
 
144
202
  model
@@ -152,12 +210,5 @@ module Volt
152
210
  @persistor = persistor.new(self)
153
211
  end
154
212
  end
155
-
156
- # Loads data in an array store persistor when data is requested.
157
- def load_data
158
- if @persistor && @persistor.is_a?(Persistors::ArrayStore)
159
- @persistor.load_data
160
- end
161
- end
162
213
  end
163
214
  end
@@ -0,0 +1,53 @@
1
+ module Volt
2
+ module Associations
3
+ module ClassMethods
4
+ def belongs_to(method_name, key_name=nil)
5
+ define_method(method_name) do
6
+ association_with_root_model('belongs_to') do |root|
7
+ # Lookup the associated model id
8
+ lookup_key = send(:"_#{key_name || method_name}_id")
9
+
10
+ # Return a promise for the belongs_to
11
+ root.send(:"_#{method_name.pluralize}").where(:_id => lookup_key).fetch_first
12
+ end
13
+ end
14
+ end
15
+
16
+ def has_many(method_name, remote_key_name=nil)
17
+ define_method(method_name) do
18
+ association_with_root_model('has_many') do |root|
19
+ id = self._id
20
+
21
+ # The key will be "{this class name}_id"
22
+ remote_key_name ||= :"#{path[-2].singularize}_id"
23
+
24
+ root.send(:"_#{method_name.pluralize}").where(remote_key_name => id)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.included(base)
31
+ base.send :extend, ClassMethods
32
+ end
33
+
34
+ private
35
+ # Currently the has_many and belongs_to associations only work on the store collection,
36
+ # this method checks to make sure we are on store and returns the root reference to it.
37
+ def association_with_root_model(method_name)
38
+ persistor = self.persistor || (respond_to(:save_to) && save_to.persistor)
39
+
40
+ # Check if we are on the store collection
41
+ if persistor.is_a?(Volt::Persistors::ModelStore)
42
+ # Get the root node
43
+ root = persistor.try(:root_model)
44
+
45
+ # Yield to the block passing in the root node
46
+ yield(root)
47
+ else
48
+ # raise an error about the method not being supported on this collection
49
+ fail "#{method_name} currently only works on the store collection (support for other collections coming soon)"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,7 +2,7 @@ module Volt
2
2
  module Buffer
3
3
  def save!
4
4
  # Compute the erros once
5
- errors = self.errors
5
+ errors = self.errors.to_h
6
6
 
7
7
  if errors.size == 0
8
8
  save_to = options[:save_to]
@@ -12,10 +12,13 @@ module Volt
12
12
  promise = save_to.append(attributes)
13
13
  else
14
14
  # We have a saved model
15
- promise = save_to.assign_attributes(attributes)
15
+ promise = save_to.assign_attributes(attributes, true)
16
16
  end
17
17
 
18
18
  return promise.then do |new_model|
19
+ # The main model saved, so mark the buffer as not new
20
+ @new = false
21
+
19
22
  if new_model
20
23
  # Set the buffer's id to track the main model's id
21
24
  attributes[:_id] = new_model._id
@@ -46,25 +49,34 @@ module Volt
46
49
  Promise.new.reject(errors)
47
50
  end
48
51
 
52
+ def buffer?
53
+ options[:buffer]
54
+ end
55
+
56
+ def save_to
57
+ options[:save_to]
58
+ end
59
+
49
60
  # Returns a buffered version of the model
50
61
  def buffer
51
62
  model_path = options[:path]
52
63
 
53
- # When we grab a buffer off of a plual class (subcollection), we get it as a model.
54
- if model_path.last.plural? && model_path[-1] != :[]
55
- model_klass = class_at_path(model_path + [:[]])
56
- else
57
- model_klass = class_at_path(model_path)
58
- end
64
+ model_klass = self.class
59
65
 
60
- new_options = options.merge(path: model_path, save_to: self).reject { |k, _| k.to_sym == :persistor }
66
+ new_options = options.merge(path: model_path, save_to: self, buffer: true).reject { |k, _| k.to_sym == :persistor }
61
67
  model = model_klass.new({}, new_options, :loading)
62
68
 
63
- if state == :loaded
69
+ if loaded?
64
70
  setup_buffer(model)
65
71
  else
66
72
  parent.then do
67
73
  setup_buffer(model)
74
+ end.fail do |err|
75
+ # TODO: right now this error gets ignored a lot, so lets log.
76
+ Volt.logger.error("error setting up buffer: #{err.inspect}")
77
+ Volt.logger.error(err.backtrace)
78
+
79
+ raise err
68
80
  end
69
81
  end
70
82
 
@@ -0,0 +1,88 @@
1
+ module Volt
2
+ # The dirty module provides helper methods for working with and tracking
3
+ # previous values on model attributes.
4
+ module Dirty
5
+ # Return the list of attributes that have changed since the last 'save' event.
6
+ def changed_attributes
7
+ @changed_attributes ||= {}
8
+ end
9
+
10
+ # Return true if key has changed
11
+ def changed?(key=nil)
12
+ if key
13
+ # return the changed values for the keys
14
+ changed_attributes.key?(key)
15
+ else
16
+ changed_attributes.present?
17
+ end
18
+ end
19
+
20
+ # Grab all previous versions of for key
21
+ def changes(key)
22
+ changed_attributes[key]
23
+ end
24
+
25
+ # Grab the previous value for the key
26
+ def was(key)
27
+ val = changed_attributes[key]
28
+
29
+ # Doing val && val[0] doesn't work in opal
30
+ # https://github.com/opal/opal/issues/664
31
+ if val
32
+ val[0]
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ # Clear changed attributes
39
+ def clear_tracked_changes!
40
+ @changed_attributes = {}
41
+ end
42
+
43
+ # Reverts the model attributes back to the pre-change values.
44
+ def revert_changes!
45
+ # Reassign the first value since we started tracking
46
+ if @changed_attributes
47
+ @changed_attributes.each_pair do |key, value|
48
+ @attributes[key] = value.first
49
+ end
50
+ end
51
+
52
+ clear_tracked_changes!
53
+ end
54
+
55
+ def attribute_will_change!(attribute_name, old_value)
56
+ # Don't track nil models
57
+ old_value = nil if old_value.nil?
58
+
59
+ (changed_attributes[attribute_name] ||= []) << old_value
60
+ end
61
+
62
+ # Handle change and was method calls
63
+ # Example: name_was or name_changes
64
+ def method_missing(method_name, *args, &block)
65
+ # Quick check to see if changes or was are being called, this check
66
+ # keeps us from needing to parse out the parts if we're not going
67
+ # to use them.
68
+ if method_name =~ /[_](changes|was)$/
69
+ # Break apart the method call
70
+ # TODO: no destructuring because of https://github.com/opal/opal/issues/663
71
+ *parts = method_name.to_s.split('_')
72
+ action = parts.pop
73
+ key = parts.join('_').to_sym
74
+
75
+ # Handle changes or was calls.
76
+ case action
77
+ when 'changes'
78
+ return changes(key)
79
+ when 'was'
80
+ return was(key)
81
+ end
82
+ end
83
+
84
+ # Otherwise, run super
85
+ super
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,21 @@
1
+ require 'volt/reactive/reactive_hash'
2
+
3
+ module Volt
4
+ class Errors < ReactiveHash
5
+ def add(field, error)
6
+ field_errors = (self[field] ||= [])
7
+ field_errors << error unless field_errors.include?(error)
8
+ end
9
+
10
+ # Merge another set of errors in
11
+ def merge!(errors)
12
+ if errors
13
+ errors.each_pair do |field, messages|
14
+ messages.each do |message|
15
+ add(field, message)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -11,7 +11,7 @@ module FieldHelpers
11
11
  end
12
12
 
13
13
  define_method(name) do
14
- read_attribute(name)
14
+ get(name)
15
15
  end
16
16
 
17
17
  define_method(:"#{name}=") do |val|
@@ -25,7 +25,7 @@ module FieldHelpers
25
25
  end
26
26
  end
27
27
 
28
- assign_attribute(name, val)
28
+ set(name, val)
29
29
  end
30
30
  end
31
31
  end
@@ -0,0 +1,17 @@
1
+ module Volt
2
+ # Included in model's so they can inform the ArrayModel when new listeners are added or removed.
3
+ module ListenerTracker
4
+
5
+ # Called when data from this model begins being watched
6
+ def listener_added
7
+ @listener_count ||= 0
8
+ @listener_count += 1
9
+ end
10
+
11
+ def listener_removed
12
+ @listener_count ||= 0
13
+ @listener_count -= 1
14
+ end
15
+
16
+ end
17
+ end