volt 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Readme.md +2 -1036
- data/VERSION +1 -1
- data/app/volt/models/user.rb +5 -0
- data/app/volt/tasks/query_tasks.rb +3 -3
- data/app/volt/tasks/store_tasks.rb +17 -15
- data/app/volt/tasks/user_tasks.rb +6 -0
- data/lib/volt/cli.rb +9 -0
- data/lib/volt/extra_core/string.rb +10 -2
- data/lib/volt/models/array_model.rb +0 -1
- data/lib/volt/models/model.rb +45 -28
- data/lib/volt/models/model_hash_behaviour.rb +16 -4
- data/lib/volt/models/model_helpers.rb +4 -2
- data/lib/volt/models/model_wrapper.rb +2 -1
- data/lib/volt/models/persistors/array_store.rb +6 -6
- data/lib/volt/models/persistors/local_store.rb +1 -1
- data/lib/volt/models/persistors/model_store.rb +3 -3
- data/lib/volt/models/persistors/query/query_listener.rb +6 -4
- data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
- data/lib/volt/models/persistors/store.rb +1 -0
- data/lib/volt/models/url.rb +20 -10
- data/lib/volt/page/bindings/template_binding.rb +7 -1
- data/lib/volt/page/page.rb +3 -3
- data/lib/volt/page/tasks.rb +20 -19
- data/lib/volt/reactive/reactive_array.rb +44 -0
- data/lib/volt/router/routes.rb +5 -0
- data/lib/volt/server.rb +3 -1
- data/lib/volt/server/component_templates.rb +7 -1
- data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/socket_connection_handler.rb +3 -0
- data/lib/volt/tasks/dispatcher.rb +3 -7
- data/lib/volt/tasks/task_handler.rb +41 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/models/model_spec.rb +32 -32
- data/spec/models/persistors/params_spec.rb +1 -1
- data/spec/router/routes_spec.rb +4 -4
- data/templates/model/model.rb.tt +3 -0
- metadata +6 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.9
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'live_query/data_store'
|
2
2
|
require_relative 'live_query/live_query_pool'
|
3
3
|
|
4
|
-
class QueryTasks
|
4
|
+
class QueryTasks < TaskHandler
|
5
5
|
@@live_query_pool = LiveQueryPool.new(DataStore.new)
|
6
6
|
@@channel_live_queries = {}
|
7
7
|
|
@@ -32,12 +32,12 @@ class QueryTasks
|
|
32
32
|
error = {:error => exception.message}
|
33
33
|
end
|
34
34
|
|
35
|
-
return initial_data, error
|
35
|
+
return [initial_data, error]
|
36
36
|
end
|
37
37
|
|
38
38
|
def initial_data
|
39
39
|
data = live_query.initial_data
|
40
|
-
data[
|
40
|
+
data[:_id] = data[:_id].to_s
|
41
41
|
|
42
42
|
return data
|
43
43
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'mongo'
|
2
|
-
require 'query_tasks'
|
3
2
|
|
4
|
-
class StoreTasks
|
3
|
+
class StoreTasks < TaskHandler
|
5
4
|
def initialize(channel=nil, dispatcher=nil)
|
6
5
|
@channel = channel
|
7
6
|
@dispatcher = dispatcher
|
@@ -11,10 +10,11 @@ class StoreTasks
|
|
11
10
|
@@db ||= Volt::DataStore.fetch
|
12
11
|
end
|
13
12
|
|
14
|
-
def
|
15
|
-
model_name = collection
|
13
|
+
def model_values_and_errors(collection, data)
|
14
|
+
model_name = collection.singularize.camelize
|
16
15
|
|
17
16
|
# TODO: Security check to make sure we have a valid model
|
17
|
+
# and don't load classes we shouldn't
|
18
18
|
begin
|
19
19
|
model_class = Object.send(:const_get, model_name)
|
20
20
|
rescue NameError => e
|
@@ -22,33 +22,35 @@ class StoreTasks
|
|
22
22
|
end
|
23
23
|
|
24
24
|
if model_class
|
25
|
-
|
25
|
+
model = model_class.new(data)
|
26
|
+
return model.to_h, model.errors
|
26
27
|
end
|
27
28
|
|
28
|
-
return {}
|
29
|
+
return data, {}
|
29
30
|
end
|
30
31
|
|
31
32
|
def save(collection, data)
|
33
|
+
puts "SAVE: #{data.inspect}"
|
32
34
|
data = data.symbolize_keys
|
33
|
-
|
34
|
-
errors = model_errors(collection, data)
|
35
|
+
values, errors = model_values_and_errors(collection, data)
|
35
36
|
|
36
37
|
if errors.size == 0
|
37
|
-
# id = BSON::ObjectId(
|
38
|
-
id =
|
38
|
+
# id = BSON::ObjectId(values[:_id])
|
39
|
+
id = values[:_id]
|
39
40
|
|
40
41
|
# Try to create
|
41
42
|
# TODO: Seems mongo is dumb and doesn't let you upsert with custom id's
|
42
43
|
begin
|
43
|
-
#
|
44
|
-
|
44
|
+
# values['_id'] = BSON::ObjectId('_id') if values['_id']
|
45
|
+
puts "VALUES: #{values.inspect}"
|
46
|
+
db[collection].insert(values)
|
45
47
|
rescue Mongo::OperationFailure => error
|
46
48
|
# Really mongo client?
|
47
49
|
if error.message[/^11000[:]/]
|
48
50
|
# Update because the id already exists
|
49
|
-
|
50
|
-
|
51
|
-
db[collection].update({:_id => id},
|
51
|
+
update_values = values.dup
|
52
|
+
update_values.delete(:_id)
|
53
|
+
db[collection].update({:_id => id}, update_values)
|
52
54
|
else
|
53
55
|
return {:error => error.message}
|
54
56
|
end
|
data/lib/volt/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'thor'
|
3
|
+
require 'volt/extra_core/extra_core'
|
3
4
|
|
4
5
|
class CLI < Thor
|
5
6
|
include Thor::Actions
|
@@ -77,6 +78,14 @@ class CLI < Thor
|
|
77
78
|
NewGem.new(self, name, options)
|
78
79
|
end
|
79
80
|
|
81
|
+
desc "model NAME COMPONENT", "Creates a model named NAME in the component named COMPONENT"
|
82
|
+
method_option :name, :type => :string, :banner => "The name of the model."
|
83
|
+
method_option :component, :type => :string, :default => 'main', :banner => "The component the model should be created in.", :required => false
|
84
|
+
def model(name, component='main')
|
85
|
+
output_file = Dir.pwd + "/app/#{component.underscore}/models/#{name.underscore.singularize}.rb"
|
86
|
+
template("model/model.rb.tt", output_file, {model_name: name.camelize.singularize})
|
87
|
+
end
|
88
|
+
|
80
89
|
def self.source_root
|
81
90
|
File.expand_path(File.join(File.dirname(__FILE__), '../../templates'))
|
82
91
|
end
|
@@ -3,12 +3,20 @@ require 'volt/extra_core/inflector'
|
|
3
3
|
class String
|
4
4
|
# TODO: replace with better implementations
|
5
5
|
# NOTE: strings are currently immutable in Opal, so no ! methods
|
6
|
+
|
7
|
+
# Turns a string into the camel case version. If it is already camel case, it should
|
8
|
+
# return the same string.
|
6
9
|
def camelize
|
7
|
-
self.
|
10
|
+
new_str = self.gsub(/_[a-z]/) { |a| a[1].upcase }
|
11
|
+
new_str = new_str[0].capitalize + new_str[1..-1]
|
12
|
+
|
13
|
+
return new_str
|
8
14
|
end
|
9
15
|
|
16
|
+
# Returns the underscore version of a string. If it is already underscore, it should
|
17
|
+
# return the same string.
|
10
18
|
def underscore
|
11
|
-
self.
|
19
|
+
self.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
|
12
20
|
end
|
13
21
|
|
14
22
|
def dasherize
|
@@ -10,7 +10,6 @@ class ArrayModel < ReactiveArray
|
|
10
10
|
|
11
11
|
attr_reader :parent, :path, :persistor, :options, :array
|
12
12
|
|
13
|
-
|
14
13
|
# For many methods, we want to call load data as soon as the model is interacted
|
15
14
|
# with, so we proxy the method, then call super.
|
16
15
|
def self.proxy_with_load_data(*method_names)
|
data/lib/volt/models/model.rb
CHANGED
@@ -26,8 +26,6 @@ class Model
|
|
26
26
|
|
27
27
|
self.send(:attributes=, attributes, true)
|
28
28
|
|
29
|
-
@cache = {}
|
30
|
-
|
31
29
|
# Models start in a loaded state since they are normally setup from an
|
32
30
|
# ArrayModel, which will have the data when they get added.
|
33
31
|
@state = :loaded
|
@@ -35,6 +33,15 @@ class Model
|
|
35
33
|
@persistor.loaded(initial_state) if @persistor
|
36
34
|
end
|
37
35
|
|
36
|
+
# the id is stored in a field named _id, so we setup _id to proxy to this
|
37
|
+
def _id
|
38
|
+
@attributes && @attributes[:_id]
|
39
|
+
end
|
40
|
+
|
41
|
+
def _id=(val)
|
42
|
+
self.__id = val
|
43
|
+
end
|
44
|
+
|
38
45
|
# Update the options
|
39
46
|
def options=(options)
|
40
47
|
@options = options
|
@@ -46,7 +53,28 @@ class Model
|
|
46
53
|
|
47
54
|
# Assign multiple attributes as a hash, directly.
|
48
55
|
def attributes=(attrs, initial_setup=false)
|
49
|
-
@attributes =
|
56
|
+
@attributes = {}
|
57
|
+
|
58
|
+
attrs = wrap_values(attrs)
|
59
|
+
|
60
|
+
if attrs
|
61
|
+
# Assign id first
|
62
|
+
id = attrs.delete(:_id)
|
63
|
+
self._id = id if id
|
64
|
+
|
65
|
+
# Assign each attribute using setters
|
66
|
+
attrs.each_pair do |key, value|
|
67
|
+
if self.respond_to?(key)
|
68
|
+
# If a method without an underscore is defined, call that.
|
69
|
+
self.send(:"#{key}=", value)
|
70
|
+
else
|
71
|
+
# Otherwise, use the _ version
|
72
|
+
self.send(:"_#{key}=", value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
@attributes = attrs
|
77
|
+
end
|
50
78
|
|
51
79
|
# Trigger and change all
|
52
80
|
@deps.changed_all!
|
@@ -90,24 +118,21 @@ class Model
|
|
90
118
|
read_attribute(method_name)
|
91
119
|
end
|
92
120
|
else
|
93
|
-
# Call
|
94
|
-
|
95
|
-
attributes.send(method_name, *args, &block)
|
121
|
+
# Call on parent
|
122
|
+
super
|
96
123
|
end
|
97
124
|
end
|
98
125
|
|
99
126
|
# Do the assignment to a model and trigger a changed event
|
100
127
|
def assign_attribute(method_name, *args, &block)
|
101
|
-
# Free any cached value
|
102
|
-
free(method_name)
|
103
|
-
|
104
128
|
self.expand!
|
105
129
|
# Assign, without the =
|
106
|
-
attribute_name = method_name[
|
130
|
+
attribute_name = method_name[1..-2].to_sym
|
107
131
|
|
108
132
|
value = args[0]
|
109
133
|
|
110
|
-
attributes[attribute_name] = wrap_value(value, [attribute_name])
|
134
|
+
@attributes[attribute_name] = wrap_value(value, [attribute_name])
|
135
|
+
|
111
136
|
@deps.changed!(attribute_name)
|
112
137
|
|
113
138
|
# Let the persistor know something changed
|
@@ -122,29 +147,26 @@ class Model
|
|
122
147
|
# Reading an attribute, we may get back a nil model.
|
123
148
|
method_name = method_name.to_sym
|
124
149
|
|
125
|
-
if method_name[0] != '_' && attributes == nil
|
150
|
+
if method_name[0] != '_' && @attributes == nil
|
126
151
|
# The method we are calling is on a nil model, return a wrapped
|
127
152
|
# exception.
|
128
153
|
return return_undefined_method(method_name)
|
129
154
|
else
|
155
|
+
attr_name = method_name[1..-1].to_sym
|
130
156
|
# See if the value is in attributes
|
131
|
-
value = (attributes && attributes[
|
132
|
-
|
133
|
-
# Also check @cache
|
134
|
-
value ||= (@cache && @cache[method_name])
|
157
|
+
value = (@attributes && @attributes[attr_name])
|
135
158
|
|
136
159
|
# Track dependency
|
137
|
-
@deps.depend(
|
160
|
+
@deps.depend(attr_name)
|
138
161
|
|
139
162
|
if value
|
140
163
|
# key was in attributes or cache
|
141
164
|
return value
|
142
165
|
else
|
143
|
-
# Cache the value, will be removed when expanded or something
|
144
|
-
# is assigned over it.
|
145
166
|
# TODO: implement a timed out cache flusing
|
146
|
-
new_model = read_new_model(
|
147
|
-
@
|
167
|
+
new_model = read_new_model(attr_name)
|
168
|
+
@attributes ||= {}
|
169
|
+
@attributes[attr_name] = new_model
|
148
170
|
|
149
171
|
return new_model
|
150
172
|
end
|
@@ -204,11 +226,6 @@ class Model
|
|
204
226
|
end
|
205
227
|
end
|
206
228
|
|
207
|
-
# Removes an item from the cache
|
208
|
-
def free(name)
|
209
|
-
@cache.delete(name)
|
210
|
-
end
|
211
|
-
|
212
229
|
# If this model is nil, it makes it into a hash model, then
|
213
230
|
# sets it up to track from the parent.
|
214
231
|
def expand!
|
@@ -217,7 +234,7 @@ class Model
|
|
217
234
|
if @parent
|
218
235
|
@parent.expand!
|
219
236
|
|
220
|
-
@parent.send(:"#{@path.last}=", self)
|
237
|
+
@parent.send(:"_#{@path.last}=", self)
|
221
238
|
end
|
222
239
|
end
|
223
240
|
end
|
@@ -264,7 +281,7 @@ class Model
|
|
264
281
|
if save_to
|
265
282
|
if save_to.is_a?(ArrayModel)
|
266
283
|
# Add to the collection
|
267
|
-
new_model = save_to
|
284
|
+
new_model = save_to << self.attributes
|
268
285
|
|
269
286
|
# Set the buffer's id to track the main model's id
|
270
287
|
self.attributes[:_id] = new_model._id
|
@@ -18,6 +18,10 @@ module ModelHashBehaviour
|
|
18
18
|
attributes.nil?
|
19
19
|
end
|
20
20
|
|
21
|
+
def empty?
|
22
|
+
!attributes || attributes.size == 0
|
23
|
+
end
|
24
|
+
|
21
25
|
def false?
|
22
26
|
attributes.false?
|
23
27
|
end
|
@@ -36,15 +40,23 @@ module ModelHashBehaviour
|
|
36
40
|
@persistor.removed(nil) if @persistor
|
37
41
|
end
|
38
42
|
|
43
|
+
def each_with_object(*args, &block)
|
44
|
+
return (@attributes || {}).each_with_object(*args, &block)
|
45
|
+
end
|
46
|
+
|
39
47
|
|
40
48
|
# Convert the model to a hash all of the way down.
|
41
49
|
def to_h
|
42
50
|
hash = {}
|
43
|
-
|
44
|
-
|
51
|
+
if empty?
|
52
|
+
return nil
|
53
|
+
else
|
54
|
+
attributes.each_pair do |key, value|
|
55
|
+
hash[key] = deep_unwrap(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
return hash
|
45
59
|
end
|
46
|
-
|
47
|
-
return hash
|
48
60
|
end
|
49
61
|
|
50
62
|
end
|
@@ -26,11 +26,13 @@ module ModelHelpers
|
|
26
26
|
begin
|
27
27
|
# remove the _ and then singularize
|
28
28
|
if path.last == :[]
|
29
|
-
|
29
|
+
index = -2
|
30
30
|
else
|
31
|
-
|
31
|
+
index = -1
|
32
32
|
end
|
33
33
|
|
34
|
+
klass_name = path[index].singularize.camelize
|
35
|
+
|
34
36
|
klass = $page.model_classes[klass_name] || Model
|
35
37
|
rescue NameError => e
|
36
38
|
# Ignore exception, just means the model isn't defined
|
@@ -17,7 +17,8 @@ module ModelWrapper
|
|
17
17
|
values = values.map {|v| wrap_value(v,lookup + [:[]]) }
|
18
18
|
elsif values.is_a?(Hash)
|
19
19
|
pairs = values.map do |k,v|
|
20
|
-
|
20
|
+
# TODO: We should be able to move wrapping into the method_missing on model
|
21
|
+
path = lookup + [k.to_sym]
|
21
22
|
|
22
23
|
[k, wrap_value(v,path)]
|
23
24
|
end
|
@@ -83,7 +83,6 @@ module Persistors
|
|
83
83
|
def run_query(model, query={})
|
84
84
|
@model.clear
|
85
85
|
|
86
|
-
# puts "Run Query: #{query.inspect}"
|
87
86
|
collection = model.path.last
|
88
87
|
# Scope to the parent
|
89
88
|
if model.path.size > 1
|
@@ -143,12 +142,13 @@ module Persistors
|
|
143
142
|
def add(index, data)
|
144
143
|
$loading_models = true
|
145
144
|
|
146
|
-
new_options = @model.options.merge(path: @model.path + [:[]], parent: @model)
|
147
|
-
|
148
145
|
# Don't add if the model is already in the ArrayModel
|
149
|
-
if !@model.array.find {|v| v
|
146
|
+
if !@model.array.find {|v| v._id == data[:_id] }
|
150
147
|
# Find the existing model, or create one
|
151
|
-
new_model = @@identity_map.find(data[
|
148
|
+
new_model = @@identity_map.find(data[:_id]) do
|
149
|
+
new_options = @model.options.merge(path: @model.path + [:[]], parent: @model)
|
150
|
+
@model.new_model(data, new_options, :loaded)
|
151
|
+
end
|
152
152
|
|
153
153
|
@model.insert(index, new_model)
|
154
154
|
end
|
@@ -193,7 +193,7 @@ module Persistors
|
|
193
193
|
if defined?($loading_models) && $loading_models
|
194
194
|
return
|
195
195
|
else
|
196
|
-
|
196
|
+
StoreTasks.delete(channel_name, model.attributes[:_id])
|
197
197
|
end
|
198
198
|
end
|
199
199
|
|
@@ -69,9 +69,9 @@ module Persistors
|
|
69
69
|
puts "Attempting to save model directly on store."
|
70
70
|
raise "Attempting to save model directly on store."
|
71
71
|
else
|
72
|
-
|
72
|
+
StoreTasks.save(collection, self_attributes).then do |errors|
|
73
73
|
if errors.size == 0
|
74
|
-
promise.resolve
|
74
|
+
promise.resolve(nil)
|
75
75
|
else
|
76
76
|
promise.reject(errors)
|
77
77
|
end
|
@@ -95,7 +95,7 @@ module Persistors
|
|
95
95
|
|
96
96
|
if model
|
97
97
|
data.each_pair do |key, value|
|
98
|
-
if key !=
|
98
|
+
if key != :_id
|
99
99
|
model.send(:"#{key}=", value)
|
100
100
|
end
|
101
101
|
end
|