volt-watch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 45e935cd6077ac6fca7fdefbbbf36b804c390bdd
4
+ data.tar.gz: 4ca467078e0bd0a7856996c417e3d5cf9b1a985d
5
+ SHA512:
6
+ metadata.gz: 7861d44ac8a441fa9656a9a3beb54a70c905a6deeb0b80ce207b890a87faebbbf3d90e3341c6b0ed5baaea4e51d3627efd85b544ef83527741ab9f31932f80a3
7
+ data.tar.gz: f99472029b43585b43d21144c06746012547be0fe582068810ca501ccbc0ebea7f62c33d4fb2a34d0286c22fd3d99f4912322649b61dcc1321f26ce07a08fb11
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ tasks
2
+ .idea
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in volt-watch.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 balmoral
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Volt::Watch
2
+
3
+ Volt::Watch is a helper mixin to provide simpler syntax and easy management of computation watches in Volt models and controllers.
4
+
5
+ Watches can can be created for attributes, elements and values of instances of Volt::Model, Volt::ArrayModel, Volt::ReactiveArray and Volt::ReactiveHash.
6
+
7
+ It further provides for creating watches on the 'internal contents' of any model, array or hash - that is,
8
+ to watch (and react to changes in) any object reachable from a 'root' model, array or hash.
9
+
10
+ It keeps track of all watches created and provides a method to stop and destroy all watches.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'volt-watch'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install volt-watch
25
+
26
+ ## Usage
27
+
28
+ Include Volt::Watch in your Volt::Model or Volt::Controller.
29
+
30
+ Then in {action}_ready create the watches.
31
+
32
+ Examples:
33
+
34
+ 1) to watch for a change in a specific attribute of a model,
35
+ ```
36
+ require 'volt-watch'
37
+ ...
38
+ module MyApp
39
+ class MainController < Volt::ModelController
40
+ include Volt::Watch
41
+ ...
42
+ def index_ready
43
+ watch do
44
+ alert "User has changed name to '#{user.name}'}
45
+ end
46
+ end
47
+ ...
48
+ end
49
+ end
50
+ ```
51
+
52
+ 2) to update a view list whenever an item in array of items in store changes:
53
+
54
+ ```
55
+ def index_ready
56
+ on_change_in ->{ store.items } do |index|
57
+ update_list_item(store.items[index], index)
58
+ end
59
+ ...
60
+ end
61
+ ```
62
+
63
+ 3) to update a chart view component when any attribute in a chart model, or any nested attribute (to any level) changes:
64
+
65
+ ```
66
+ def index_ready
67
+ on_deep_change_in ->{ page._chart } do |model, locus, value|
68
+ # `model` may be page._chart or any of its attributes or their attributes
69
+ # which are models or arrays.
70
+ # `locus` identifies where the change in the model or array has occurred
71
+ # as a symbol (for a model attribute) or integer (for an array element's index).
72
+ # `value` is the new value.
73
+ # If an array has changed size then `model` will be the array,
74
+ # `locus` will be `:size` and `value` will be the new size.
75
+ update_chart_view(model, locus, value)
76
+ end
77
+ ...
78
+ end
79
+ ```
80
+
81
+ **IMPORTANT**
82
+
83
+ Watches should be terminated when no longer required, such as when the page is left.
84
+
85
+ To terminate all watches call `stop_watches`.
86
+
87
+ For example:
88
+
89
+ ```
90
+ def before_index_remove
91
+ stop_watches
92
+ end
93
+ ```
94
+
95
+ ## Contributing
96
+
97
+ 1. Fork it ( http://github.com/[my-github-username]/volt-watch/fork )
98
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
99
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
100
+ 4. Push to the branch (`git push origin my-new-feature`)
101
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ module Volt
2
+ module Watch
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
data/lib/volt/watch.rb ADDED
@@ -0,0 +1,328 @@
1
+ module Volt
2
+ module Watch
3
+
4
+ # Add reactivity to the given proc.
5
+ #
6
+ # The proc will be called if any reactive
7
+ # attribute accessed in the proc changes.
8
+ #
9
+ # For example:
10
+ #
11
+ # ```
12
+ # watch do
13
+ # puts person._name
14
+ # end
15
+ # ```
16
+ #
17
+ # The behaviour is identical to doing
18
+ #
19
+ # ```
20
+ # ->{ puts person._name }.watch!
21
+ # ```
22
+ #
23
+ # Alias is :watch
24
+ def watch(proc = nil, &block)
25
+ add_watch(proc || block)
26
+ end
27
+ alias_method :activate, :watch
28
+
29
+
30
+ # Adds a watch for a shallow change in the contents
31
+ # (attributes, elements, or key-value pairs) of a
32
+ # Volt::Model, Volt::ArrayModel, Volt::ReactiveArray)
33
+ # or Volt::ReactiveHash.
34
+ #
35
+ # When any value in the model, array or hash changes then
36
+ # the given block will be called.
37
+ #
38
+ # The values of a Volt::Model are its attributes.
39
+ #
40
+ # The values of an Volt::ArrayModel are its elements.
41
+ #
42
+ # The values of an Volt::ReactiveHash are its key-value pairs.
43
+ #
44
+ # The attribute_name/index/key of the changed value will be
45
+ # passed as the first argument to the given block. The new
46
+ # associated value will be passed as the second argument.
47
+ #
48
+ # For example:
49
+ #
50
+ # ```
51
+ # on_change_in ->{ user } do |attr, value|
52
+ # puts "user.#{attr} => #{value}"
53
+ # end
54
+ # ```
55
+ # or
56
+ #
57
+ # ```
58
+ # on_change_in ->{ user } do |attr|
59
+ # puts "user.#{attr} => #{user.get(attr)}"
60
+ # end
61
+ # ```
62
+ # or
63
+ #
64
+ # ```
65
+ # on_change_in ->{ page._items} do |index, value|
66
+ # puts "page[#{index}] => #{item}"
67
+ # end
68
+ # ```
69
+ # or
70
+ #
71
+ # ```
72
+ # on_change_in ->{ page._items} do |index|
73
+ # puts "page[#{index}] => #{page._items[index]}"
74
+ # end
75
+ # ```
76
+ # or
77
+ #
78
+ # ```
79
+ # on_change_in ->{ store.dictionary } do |key, entry|
80
+ # puts "dictionary[#{key}] => #{entry}"
81
+ # end
82
+ # ```
83
+ # or
84
+ # ```
85
+ # on_change_in ->{ store.dictionary } do |key|
86
+ # puts "dictionary[#{key}] => #{store.dictionary[key]}"
87
+ # end
88
+ # ```
89
+ def on_change_in(*args, except: nil, &block)
90
+ args.each do |arg|
91
+ ensure_reactive(arg)
92
+ traverse(arg, :shallow, except, block)
93
+ end
94
+ end
95
+
96
+ # Does a deep traversal of all values reachable from
97
+ # the given root object(s).
98
+ #
99
+ # Such values include:
100
+ # * attributes and field values of Volt::Model's
101
+ # * size and elements of Volt::ArrayModel's
102
+ # * size and elements of Volt::ReactiveArray's
103
+ # * size and key-value pairs of Volt::ReactiveHash's
104
+ # * nested values of the above
105
+ #
106
+ # The root(s) may be a Volt::Model, Volt::ArrayModel,
107
+ # Volt::ReactiveArray or Volt::ReactiveHash.
108
+ #
109
+ # If the given block accepts zero or one argument then
110
+ # the block will be called with the root object as the argument
111
+ # whenever any change occurs at any depth. This mode is
112
+ # suitable when watching for deep changes to the contents
113
+ # of a model/array/hash but you DO NOT need to identify
114
+ # the particular value that changed.
115
+ #
116
+ # If the given block accepts two or more arguments then
117
+ # the block will be called when any value reachable from
118
+ # (one of) the root(s) changes and will be passed three arguments:
119
+ # 1. the parent (owner) of the value that changed
120
+ # i.e. the model, array or hash holding the value
121
+ # 2. the locus of the value, either:
122
+ # * the attribute or field name for a model
123
+ # * the index in an array
124
+ # * the key in a hash
125
+ # * the symbol :size if array or hash size changes
126
+ # 3. the new value
127
+ # The block may choose to accept 2 or 3 arguments.
128
+ # This mode is suitable when watching for deep changes
129
+ # to the contents of a model/array/hash and you DO need
130
+ # to identify what value that changed.
131
+ #
132
+ # In both modes, an optional argument specifying attributes
133
+ # you don't want to watch may be given with the :except
134
+ # keyword argument. The argument should be a symbol or
135
+ # integer or array of symbols or integers matching model
136
+ # attributes, array indexes or hash keys which you wish
137
+ # to ignore changes to. It may also include `:size` if you
138
+ # wish to ignore changes to the size of arrays or hashes.
139
+ # TODO: make :except more precise, perhaps with pairs of
140
+ # [parent, locus] to identify exceptions more accurately.
141
+ # Also allow for [Class, locus] to except any object of
142
+ # the given class.
143
+ #
144
+ # For example:
145
+ #
146
+ # ```
147
+ # class Contact < Volt::Model
148
+ # field :street
149
+ # field :city
150
+ # field :zip
151
+ # field :country
152
+ # field :phone
153
+ # field :email
154
+ # end
155
+ #
156
+ # class Customer < Volt::Model
157
+ # field :name
158
+ # field :contact
159
+ # end
160
+ #
161
+ # class Order < Volt::Model
162
+ # field :customer
163
+ # field :product
164
+ # field :date
165
+ # field :quantity
166
+ # end
167
+ #
168
+ # ...
169
+ #
170
+ # def shallow_order_watch
171
+ # # one argument in given block has no detail of change
172
+ # on_deep_change_in orders do |store._orders|
173
+ # puts "something unknown changed in orders"
174
+ # end
175
+ # end
176
+ #
177
+ # def on_deep_change_in
178
+ # # three arguments in given block gives detail of change
179
+ # on_deep_change_in store._orders do |context, locus, value|
180
+ # case
181
+ # when context == store._orders
182
+ # if locus == :size
183
+ # puts "orders.size has changed to #{value}"
184
+ # else
185
+ # index = locus
186
+ # puts "orders[#{index}] has changed to #{value}"
187
+ # end
188
+ # when context.is_a? Order
189
+ # order, attr = context, locus
190
+ # puts "Order[#{order.id}].#{attr} has changed to #{value}"
191
+ # when context.is_a? Customer
192
+ # customer, attr = context, locus
193
+ # puts "customer #{customer.id} #{attr} has changed to #{value}"
194
+ # end
195
+ # end
196
+ # end
197
+ # ```
198
+ #
199
+ def on_deep_change_in(*roots, except: nil, &block)
200
+ roots.each do |root|
201
+ ensure_reactive(root)
202
+ if block.arity <= 1
203
+ add_watch( ->{ traverse(root, :root, except, block) } )
204
+ else
205
+ traverse(root, :node, except, block)
206
+ end
207
+ end
208
+ end
209
+
210
+ # Stops and destroys all current watches.
211
+ # Call when watches are no longer required.
212
+ # Should typically be called when leaving a page,
213
+ # for example in `before_{action}_remove`
214
+ def stop_watches
215
+ if @watches
216
+ @watches.each do |b|
217
+ b.stop
218
+ end
219
+ @watches = nil
220
+ end
221
+ end
222
+
223
+ private
224
+
225
+ def ensure_reactive(model)
226
+ unless reactive?(model)
227
+ raise ArgumentError, 'argument must be Volt Model, ArrayModel, ReactiveArray or ReactiveHash'
228
+ end
229
+ end
230
+
231
+ def reactive?(model)
232
+ Volt::Model === model ||
233
+ Volt::ArrayModel === model ||
234
+ Volt::ReactiveArray == model ||
235
+ Volt::ReactiveHash === model
236
+ end
237
+
238
+ def traverse(node, mode, except, block)
239
+ if node.is_a?(Volt::Model)
240
+ traverse_model(node, mode, except, block)
241
+ elsif node.is_a?(Volt::ReactiveArray)
242
+ traverse_array(node, mode, except, block)
243
+ elsif node.is_a?(Volt::ReactiveHash)
244
+ traverse_hash(node, mode, except, block)
245
+ end
246
+ end
247
+
248
+ def traverse_array(array, mode, except, block)
249
+ compute_size(hash, mode, except, block)
250
+ array.size.times do |i|
251
+ # must access through array[i] to trigger dependency
252
+ compute_value(array, i, ->{ array[i] }, mode, except, block)
253
+ end
254
+ unless mode == :shallow
255
+ array.size.times do |i|
256
+ traverse(array[i], mode, except, block)
257
+ end
258
+ end
259
+ end
260
+
261
+ def traverse_hash(hash, mode, except, block)
262
+ compute_size(hash, mode, except, block)
263
+ hash.each_key do |key|
264
+ # must access through hash[key] to trigger dependency
265
+ compute_value(hash, key, ->{ hash[key] }, mode, except, block)
266
+ end
267
+ unless mode == :shallow
268
+ hash.each_value do |value|
269
+ traverse(value, mode, except, block)
270
+ end
271
+ end
272
+ end
273
+
274
+ def traverse_model(model, mode, except, block)
275
+ traverse_model_attrs(model, mode, except, block)
276
+ traverse_model_fields(model, mode, except, block)
277
+ end
278
+
279
+ def traverse_model_attrs(model, mode, except, block)
280
+ model.attributes.each_key do |attr|
281
+ # must access through get(attr) to trigger dependency
282
+ compute_value(model, attr, ->{ model.get(attr) }, mode, except, block)
283
+ end
284
+ unless mode == :shallow
285
+ model.attributes.each_key do |attr|
286
+ traverse(model.get(:"#{attr}"), mode, except, block)
287
+ end
288
+ end
289
+ end
290
+
291
+ def traverse_model_fields(model, mode, except, block)
292
+ fields = model.class.fields_data
293
+ if fields
294
+ fields.each_key do |attr|
295
+ # must access through send(attr) to trigger dependency
296
+ compute_value(model, attr, ->{ model.send(attr) }, mode, except, block)
297
+ end
298
+ unless mode == :shallow
299
+ fields.each_key do |attr|
300
+ traverse(model.send(attr), mode, except, block)
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ def compute_value(parent, locus, value, mode, except, block)
307
+ unless except && except.include?(locus)
308
+ compute_term mode, ->{ block.call(parent, locus, value.call) }
309
+ end
310
+ end
311
+
312
+ def compute_size(collection, mode, except, block)
313
+ unless except && except.include?(:size)
314
+ compute_term mode, ->{ block.call(collection, :size, collection.size) }
315
+ end
316
+ end
317
+
318
+ def compute_term(mode, proc)
319
+ # :shallow and :node should watch, :root doesn't
320
+ mode == :root ? proc.call : add_watch(proc)
321
+ end
322
+
323
+ def add_watch(proc)
324
+ (@watches ||= []) << proc.watch!
325
+ end
326
+
327
+ end
328
+ end
data/lib/volt-watch.rb ADDED
@@ -0,0 +1 @@
1
+ require 'volt/watch'
@@ -0,0 +1,14 @@
1
+ # Volt sets up rspec and capybara for testing.
2
+ require 'volt/spec/setup'
3
+ Volt.spec_setup
4
+
5
+ RSpec.configure do |config|
6
+ config.run_all_when_everything_filtered = true
7
+ config.filter_run :focus
8
+
9
+ # Run specs in random order to surface order dependencies. If you find an
10
+ # order dependency and want to debug it, you can fix the order by providing
11
+ # the seed, which is printed after each run.
12
+ # --seed 1234
13
+ config.order = 'random'
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Volt::Watch do
4
+ it 'should have a version number' do
5
+ expect(Volt::Watch::VERSION).not_to be nil
6
+ end
7
+
8
+ it 'should do something useful' do
9
+ expect(false).to be true
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'volt/watch/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'volt-watch'
8
+ spec.version = Volt::Watch::VERSION
9
+ spec.authors = ['Colin Gunn']
10
+ spec.email = ['colgunn@icloud.com']
11
+ spec.summary = %q{Helper plugin for to provide easy reactivity bindings in Volt models and controllers.}
12
+ spec.homepage = 'https://github.com/balmoral/volt-watch'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'volt', '~> 0.9.5.0'
21
+ spec.add_development_dependency 'rspec', '~> 3.2.0'
22
+ spec.add_development_dependency 'rake'
23
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: volt-watch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Colin Gunn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: volt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.5.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - colgunn@icloud.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/volt-watch.rb
68
+ - lib/volt/watch.rb
69
+ - lib/volt/watch/version.rb
70
+ - spec/spec_helper.rb
71
+ - spec/volt/watch_spec.rb
72
+ - volt-watch.gemspec
73
+ homepage: https://github.com/balmoral/volt-watch
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.6
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Helper plugin for to provide easy reactivity bindings in Volt models and
97
+ controllers.
98
+ test_files:
99
+ - spec/spec_helper.rb
100
+ - spec/volt/watch_spec.rb
101
+ has_rdoc: