volt 0.4.9 → 0.4.10
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 +4 -4
- data/VERSION +1 -1
- data/app/volt/tasks/channel_tasks.rb +2 -2
- data/lib/volt/console.rb +0 -1
- data/lib/volt/controllers/model_controller.rb +20 -2
- data/lib/volt/models.rb +4 -2
- data/lib/volt/models/array_model.rb +19 -5
- data/lib/volt/models/model.rb +51 -9
- data/lib/volt/models/model_wrapper.rb +2 -2
- data/lib/volt/models/persistors/array_store.rb +79 -0
- data/lib/volt/models/persistors/base.rb +24 -0
- data/lib/volt/models/persistors/model_store.rb +140 -0
- data/lib/volt/models/persistors/params.rb +26 -0
- data/lib/volt/models/persistors/store.rb +44 -0
- data/lib/volt/models/persistors/store_factory.rb +15 -0
- data/lib/volt/models/url.rb +2 -1
- data/lib/volt/page/bindings/each_binding.rb +2 -2
- data/lib/volt/page/channel.rb +0 -2
- data/lib/volt/page/page.rb +2 -3
- data/lib/volt/page/tasks.rb +28 -7
- data/lib/volt/reactive/reactive_array.rb +14 -1
- data/lib/volt/tasks/dispatcher.rb +8 -2
- data/spec/models/model_spec.rb +32 -2
- data/spec/models/persistors/params_spec.rb +16 -0
- data/spec/models/persistors/store_spec.rb +29 -0
- data/spec/models/store_spec.rb +16 -16
- data/spec/router/routes_spec.rb +1 -1
- data/templates/project/app/home/controllers/index_controller.rb +2 -1
- metadata +12 -8
- data/lib/volt/models/params.rb +0 -72
- data/lib/volt/models/params_array.rb +0 -9
- data/lib/volt/models/store.rb +0 -183
- data/lib/volt/models/store_array.rb +0 -82
- data/spec/models/params_spec.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11fd8a0c37e1e364ca98038498604a3b897aa391
|
4
|
+
data.tar.gz: 6aafe7c02f864bc4da06dffe7c380675c92b9931
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74414b633710710e1d06d2124b7f9101ebee14170eab47e40fa1f7d8733e857f63a19d1d65e08c1c9c8003c2bc317e4437c323f51ccb98d19c953a25cbdd6633
|
7
|
+
data.tar.gz: 477753ecfdf1a92b631544c98622f46a686eb3d850fb16f9ff48f811232d51d396c0001de9f05dde62943d4d47d65d3b06ece931dfa256a266a26a6680a9bd8e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.10
|
@@ -7,7 +7,7 @@ class ChannelTasks
|
|
7
7
|
@channel = channel
|
8
8
|
end
|
9
9
|
|
10
|
-
def add_listener(channel_name)
|
10
|
+
def add_listener(channel_name, scope={})
|
11
11
|
# Track every channel that is listening
|
12
12
|
@@listeners[channel_name] ||= []
|
13
13
|
@@listeners[channel_name] << @channel
|
@@ -18,7 +18,7 @@ class ChannelTasks
|
|
18
18
|
@@channel_listeners[@channel][channel_name] = true
|
19
19
|
end
|
20
20
|
|
21
|
-
def remove_listener(channel_name)
|
21
|
+
def remove_listener(channel_name, scope={})
|
22
22
|
if @@listeners[channel_name]
|
23
23
|
@@listeners[channel_name].delete(@channel)
|
24
24
|
if @@channel_listeners[@channel]
|
data/lib/volt/console.rb
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
class ModelController
|
2
|
-
def initialize
|
3
|
-
|
2
|
+
def initialize
|
3
|
+
self.model = @@default_model
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.model(val)
|
7
|
+
@@default_model = val
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sets the current model on this controller
|
11
|
+
def model=(val)
|
12
|
+
if val.is_a?(Symbol) || val.is_a?(String)
|
13
|
+
collections = [:page, :store, :params]
|
14
|
+
if collections.include?(val.to_sym)
|
15
|
+
@model = self.send(val)
|
16
|
+
else
|
17
|
+
raise "#{val} is not the name of a valid model, choose from: #{collections.join(', ')}"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@model = model
|
21
|
+
end
|
4
22
|
end
|
5
23
|
|
6
24
|
def page
|
data/lib/volt/models.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'volt/extra_core/extra_core'
|
2
2
|
require 'volt/reactive/reactive_value'
|
3
3
|
require 'volt/models/model'
|
4
|
-
require 'volt/models/
|
5
|
-
require 'volt/models/
|
4
|
+
require 'volt/models/persistors/store_factory'
|
5
|
+
require 'volt/models/persistors/array_store'
|
6
|
+
require 'volt/models/persistors/model_store'
|
7
|
+
require 'volt/models/persistors/params'
|
6
8
|
|
@@ -3,15 +3,19 @@ require 'volt/models/model_wrapper'
|
|
3
3
|
class ArrayModel < ReactiveArray
|
4
4
|
include ModelWrapper
|
5
5
|
|
6
|
-
attr_reader :parent, :path
|
6
|
+
attr_reader :parent, :path, :persistor, :options
|
7
7
|
|
8
|
-
def initialize(array=[],
|
9
|
-
@
|
10
|
-
@
|
8
|
+
def initialize(array=[], options={})
|
9
|
+
@options = options
|
10
|
+
@parent = options[:parent]
|
11
|
+
@path = options[:path] || []
|
12
|
+
@persistor = setup_persistor(options[:persistor])
|
11
13
|
|
12
14
|
array = wrap_values(array)
|
13
15
|
|
14
16
|
super(array)
|
17
|
+
|
18
|
+
@persistor.loaded if @persistor
|
15
19
|
end
|
16
20
|
|
17
21
|
def attributes
|
@@ -23,6 +27,8 @@ class ArrayModel < ReactiveArray
|
|
23
27
|
args = wrap_values(args)
|
24
28
|
|
25
29
|
super(*args)
|
30
|
+
|
31
|
+
@persistor.added(args[0]) if @persistor
|
26
32
|
end
|
27
33
|
|
28
34
|
# Make sure it gets wrapped
|
@@ -36,7 +42,7 @@ class ArrayModel < ReactiveArray
|
|
36
42
|
args = wrap_values(args)
|
37
43
|
super(*args)
|
38
44
|
end
|
39
|
-
|
45
|
+
|
40
46
|
def new_model(*args)
|
41
47
|
Model.new(*args)
|
42
48
|
end
|
@@ -44,4 +50,12 @@ class ArrayModel < ReactiveArray
|
|
44
50
|
def new_array_model(*args)
|
45
51
|
ArrayModel.new(*args)
|
46
52
|
end
|
53
|
+
|
54
|
+
private
|
55
|
+
# Takes the persistor if there is one and
|
56
|
+
def setup_persistor(persistor)
|
57
|
+
if persistor
|
58
|
+
@persistor = persistor.new(self)
|
59
|
+
end
|
60
|
+
end
|
47
61
|
end
|
data/lib/volt/models/model.rb
CHANGED
@@ -18,7 +18,7 @@ class Model
|
|
18
18
|
include ObjectTracking
|
19
19
|
|
20
20
|
attr_accessor :attributes
|
21
|
-
attr_reader :parent, :path
|
21
|
+
attr_reader :parent, :path, :persistor, :options
|
22
22
|
|
23
23
|
def nil?
|
24
24
|
attributes.nil?
|
@@ -32,15 +32,27 @@ class Model
|
|
32
32
|
attributes.true?
|
33
33
|
end
|
34
34
|
|
35
|
-
def initialize(attributes={},
|
36
|
-
@
|
37
|
-
@
|
35
|
+
def initialize(attributes={}, options={})
|
36
|
+
@options = options
|
37
|
+
@parent = options[:parent]
|
38
|
+
@path = options[:path] || []
|
39
|
+
@class_paths = options[:class_paths]
|
40
|
+
@persistor = setup_persistor(options[:persistor])
|
41
|
+
|
38
42
|
self.attributes = wrap_values(attributes)
|
43
|
+
|
44
|
+
@persistor.loaded if @persistor
|
39
45
|
end
|
40
46
|
|
41
47
|
# Pass the comparison through
|
42
48
|
def ==(val)
|
43
|
-
|
49
|
+
if val.is_a?(Model)
|
50
|
+
# Use normal comparison for a model
|
51
|
+
return super
|
52
|
+
else
|
53
|
+
# Compare to attributes otherwise
|
54
|
+
return attributes == val
|
55
|
+
end
|
44
56
|
end
|
45
57
|
|
46
58
|
# Pass through needed
|
@@ -48,6 +60,17 @@ class Model
|
|
48
60
|
!attributes
|
49
61
|
end
|
50
62
|
|
63
|
+
# Pass to the persisotr
|
64
|
+
def event_added(event, scope_provider, first)
|
65
|
+
@persistor.event_added(event, scope_provider, first) if @persistor
|
66
|
+
end
|
67
|
+
|
68
|
+
# Pass to the persistor
|
69
|
+
def event_removed(event, no_more_events)
|
70
|
+
@persistor.event_removed(event, no_more_events) if @persistor
|
71
|
+
end
|
72
|
+
|
73
|
+
|
51
74
|
tag_method(:delete) do
|
52
75
|
destructive!
|
53
76
|
end
|
@@ -88,6 +111,9 @@ class Model
|
|
88
111
|
|
89
112
|
attributes[attribute_name] = wrap_value(value, [attribute_name])
|
90
113
|
trigger_by_attribute!('changed', attribute_name)
|
114
|
+
|
115
|
+
# Let the persistor know something changed
|
116
|
+
@persistor.changed(attribute_name) if @persistor
|
91
117
|
end
|
92
118
|
|
93
119
|
# When reading an attribute, we need to handle reading on:
|
@@ -112,7 +138,11 @@ class Model
|
|
112
138
|
|
113
139
|
# Get a new model, make it easy to override
|
114
140
|
def read_new_model(method_name)
|
115
|
-
|
141
|
+
if @persistor && @persistor.respond_to?(:read_new_model)
|
142
|
+
@persistor.read_new_model(method_name)
|
143
|
+
else
|
144
|
+
return new_model(nil, @options.merge(parent: self, path: path + [method_name]))
|
145
|
+
end
|
116
146
|
end
|
117
147
|
|
118
148
|
def return_undefined_method(method_name)
|
@@ -168,7 +198,11 @@ class Model
|
|
168
198
|
end
|
169
199
|
# Initialize an empty array and append to it
|
170
200
|
def <<(value)
|
171
|
-
@parent
|
201
|
+
if @parent
|
202
|
+
@parent.expand!
|
203
|
+
else
|
204
|
+
raise "Model data should be stored in sub collections."
|
205
|
+
end
|
172
206
|
|
173
207
|
# Grab the last section of the path, so we can do the assign on the parent
|
174
208
|
path = @path.last
|
@@ -176,7 +210,7 @@ class Model
|
|
176
210
|
|
177
211
|
if result.nil?
|
178
212
|
# If this isn't a model yet, instantiate it
|
179
|
-
@parent.send(:"#{path}=", new_array_model([], @
|
213
|
+
@parent.send(:"#{path}=", new_array_model([], @options))
|
180
214
|
result = @parent.send(path)
|
181
215
|
end
|
182
216
|
|
@@ -187,7 +221,7 @@ class Model
|
|
187
221
|
end
|
188
222
|
|
189
223
|
def inspect
|
190
|
-
"<#{self.class.to_s} #{attributes.inspect}>"
|
224
|
+
"<#{self.class.to_s}:#{object_id} #{attributes.inspect}>"
|
191
225
|
end
|
192
226
|
|
193
227
|
|
@@ -214,4 +248,12 @@ class Model
|
|
214
248
|
trigger_by_attribute!(event, key, *args)
|
215
249
|
end
|
216
250
|
end
|
251
|
+
|
252
|
+
# Takes the persistor if there is one and
|
253
|
+
def setup_persistor(persistor)
|
254
|
+
if persistor
|
255
|
+
@persistor = persistor.new(self)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
217
259
|
end
|
@@ -3,9 +3,9 @@ module ModelWrapper
|
|
3
3
|
# model.
|
4
4
|
def wrap_value(value, lookup)
|
5
5
|
if value.cur.is_a?(Array)
|
6
|
-
value = new_array_model(value, self, path + lookup)
|
6
|
+
value = new_array_model(value, @options.merge(parent: self, path: path + lookup))
|
7
7
|
elsif value.cur.is_a?(Hash)
|
8
|
-
value = new_model(value, self, path + lookup)
|
8
|
+
value = new_model(value, @options.merge(parent: self, path: path + lookup))
|
9
9
|
end
|
10
10
|
|
11
11
|
return value
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'volt/models/persistors/store'
|
2
|
+
|
3
|
+
module Persistors
|
4
|
+
class ArrayStore < Store
|
5
|
+
|
6
|
+
# Called when a collection loads
|
7
|
+
def loaded
|
8
|
+
scope = {}
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# Scope to the parent
|
13
|
+
if @model.path.size > 1
|
14
|
+
parent = @model.parent
|
15
|
+
|
16
|
+
parent.persistor.ensure_setup if parent.persistor
|
17
|
+
puts @model.parent.inspect
|
18
|
+
|
19
|
+
if parent && (attrs = parent.attributes) && attrs[:_id].true?
|
20
|
+
scope[:"#{@model.path[-3].singularize}_id"] = attrs[:_id]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Load At Scope: #{scope.inspect}"
|
25
|
+
|
26
|
+
query(scope)
|
27
|
+
|
28
|
+
change_channel_connection('add', 'added')
|
29
|
+
change_channel_connection('add', 'removed')
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(query)
|
33
|
+
@tasks.call('StoreTasks', 'find', @model.path.last, query) do |results|
|
34
|
+
# TODO: Globals evil, replace
|
35
|
+
$loading_models = true
|
36
|
+
|
37
|
+
new_options = @model.options.merge(path: @model.path + [:[]], parent: @model)
|
38
|
+
|
39
|
+
results.each do |result|
|
40
|
+
@model << Model.new(result, new_options)
|
41
|
+
end
|
42
|
+
$loading_models = false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def channel_name
|
47
|
+
@model.path[-1]
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# When a model is added to this collection, we call its "changed"
|
52
|
+
# method. This should trigger a save.
|
53
|
+
def added(model)
|
54
|
+
unless $loading_models
|
55
|
+
model.persistor.changed
|
56
|
+
end
|
57
|
+
|
58
|
+
if model.persistor
|
59
|
+
# Tell the persistor it was added
|
60
|
+
model.persistor.add_to_collection
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def removed(model)
|
65
|
+
if model.persistor
|
66
|
+
# Tell the persistor it was removed
|
67
|
+
model.persistor.remove_from_collection
|
68
|
+
end
|
69
|
+
|
70
|
+
if $loading_models
|
71
|
+
return
|
72
|
+
else
|
73
|
+
puts "delete #{channel_name} - #{model.attributes[:_id]}"
|
74
|
+
@tasks.call('StoreTasks', 'delete', channel_name, model.attributes[:_id])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Persistors
|
2
|
+
# Implements the base persistor functionality.
|
3
|
+
class Base
|
4
|
+
def loaded
|
5
|
+
end
|
6
|
+
|
7
|
+
def changed(attribute_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def added(model)
|
11
|
+
end
|
12
|
+
|
13
|
+
# For removed, the default action is to call changed for it
|
14
|
+
def removed(attribute_name)
|
15
|
+
changed(attribute_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def event_added(event, scope_provider, first)
|
19
|
+
end
|
20
|
+
|
21
|
+
def event_removed(event, no_more_events)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'volt/models/persistors/store'
|
2
|
+
|
3
|
+
module Persistors
|
4
|
+
class ModelStore < Store
|
5
|
+
ID_CHARS = [('a'..'z'), ('A'..'Z'), ('0'..'9')].map {|v| v.to_a }.flatten
|
6
|
+
|
7
|
+
@@identity_map = {}
|
8
|
+
|
9
|
+
attr_reader :model
|
10
|
+
|
11
|
+
def add_to_collection
|
12
|
+
@in_collection = true
|
13
|
+
ensure_setup
|
14
|
+
changed
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_from_collection
|
18
|
+
@in_collection = false
|
19
|
+
stop_listening_for_changes
|
20
|
+
end
|
21
|
+
|
22
|
+
# Called the first time a value is assigned into this model
|
23
|
+
def ensure_setup
|
24
|
+
if @model.attributes
|
25
|
+
@model.attributes[:_id] ||= generate_id
|
26
|
+
|
27
|
+
if !model_in_identity_map?
|
28
|
+
@@identity_map[@model.attributes[:_id]] ||= self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check to see if we already have listeners setup
|
32
|
+
if @model.listeners[:changed]
|
33
|
+
listen_for_changes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def model_in_identity_map?
|
39
|
+
@@identity_map[@model.attributes[:_id]]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a random unique id that can be used as the mongo id as well
|
43
|
+
def generate_id
|
44
|
+
id = []
|
45
|
+
12.times { id << ID_CHARS.sample }
|
46
|
+
|
47
|
+
return id.join
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called when the model changes
|
51
|
+
def changed(attribute_name=nil)
|
52
|
+
# puts "CHANGED: #{attribute_name.inspect} - #{@model.inspect}"
|
53
|
+
ensure_setup
|
54
|
+
|
55
|
+
path_size = @model.path.size
|
56
|
+
if !(defined?($loading_models) && $loading_models) && @tasks && path_size > 0 && !@model.nil?
|
57
|
+
if path_size > 3 && (parent = @model.parent) && source = parent.parent
|
58
|
+
@model.attributes[:"#{@model.path[-4].singularize}_id"] = source._id
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "Save: #{collection} - #{self_attributes.inspect} - #{@model.path.inspect}"
|
62
|
+
@tasks.call('StoreTasks', 'save', collection, self_attributes)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def listen_for_changes
|
67
|
+
unless @change_listening
|
68
|
+
if @in_collection
|
69
|
+
@change_listening = true
|
70
|
+
change_channel_connection("add")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop_listening_for_changes
|
76
|
+
if @change_listening
|
77
|
+
@change_listening = false
|
78
|
+
change_channel_connection("remove")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def event_added(event, scope_provider, first)
|
83
|
+
if first && event == :changed
|
84
|
+
# Start listening
|
85
|
+
ensure_setup
|
86
|
+
listen_for_changes
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def event_removed(event, no_more_events)
|
91
|
+
if no_more_events && event == :changed
|
92
|
+
# Stop listening
|
93
|
+
stop_listening_for_changes
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def channel_name
|
98
|
+
@channel_name ||= "#{@model.path[-2]}##{@model.attributes[:_id]}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Finds the model in its parent collection and deletes it.
|
102
|
+
def delete!
|
103
|
+
if @model.path.size == 0
|
104
|
+
raise "Not in a collection"
|
105
|
+
end
|
106
|
+
|
107
|
+
@model.parent.delete(@model)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Update the models based on the id/identity map. Usually these requests
|
111
|
+
# will come from the backend.
|
112
|
+
def self.update(model_id, data)
|
113
|
+
persistor = @@identity_map[model_id]
|
114
|
+
|
115
|
+
if persistor
|
116
|
+
data.each_pair do |key, value|
|
117
|
+
if key != '_id'
|
118
|
+
persistor.model.send(:"#{key}=", value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.from_id(id)
|
125
|
+
@@identity_map[id]
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
# Return the attributes that are only for this store, not any sub-associations.
|
130
|
+
def self_attributes
|
131
|
+
# Don't store any sub-stores, those will do their own saving.
|
132
|
+
@model.attributes.reject {|k,v| v.is_a?(Model) || v.is_a?(ArrayModel) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def collection
|
136
|
+
@model.path[-2]
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|