volt 0.8.8 → 0.8.9
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/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
|