volt-watch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: