volt 0.9.5.pre4 → 0.9.5.pre5
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 +10 -0
- data/README.md +13 -5
- data/app/volt/assets/css/{notices.css.scss → notices.scss} +0 -0
- data/app/volt/models/active_volt_instance.rb +1 -1
- data/app/volt/tasks/live_query/live_query.rb +11 -3
- data/app/volt/tasks/store_tasks.rb +14 -17
- data/lib/volt/cli.rb +22 -0
- data/lib/volt/cli/asset_compile.rb +63 -63
- data/lib/volt/cli/base_index_renderer.rb +26 -0
- data/lib/volt/cli/generate.rb +1 -1
- data/lib/volt/config.rb +1 -0
- data/lib/volt/controllers/model_controller.rb +37 -1
- data/lib/volt/extra_core/array.rb +22 -0
- data/lib/volt/models/array_model.rb +7 -1
- data/lib/volt/models/errors.rb +1 -1
- data/lib/volt/models/field_helpers.rb +36 -21
- data/lib/volt/models/model.rb +16 -0
- data/lib/volt/models/validations/validations.rb +21 -6
- data/lib/volt/models/validators/type_validator.rb +35 -3
- data/lib/volt/page/bindings/content_binding.rb +1 -1
- data/lib/volt/page/bindings/event_binding.rb +40 -16
- data/lib/volt/page/document_events.rb +8 -6
- data/lib/volt/reactive/reactive_array.rb +18 -1
- data/lib/volt/server/forking_server.rb +7 -1
- data/lib/volt/server/html_parser/attribute_scope.rb +26 -0
- data/lib/volt/server/html_parser/component_view_scope.rb +30 -22
- data/lib/volt/server/middleware/default_middleware_stack.rb +6 -1
- data/lib/volt/server/rack/asset_files.rb +5 -3
- data/lib/volt/server/rack/opal_files.rb +35 -23
- data/lib/volt/server/rack/sprockets_helpers_setup.rb +71 -0
- data/lib/volt/server/template_handlers/view_processor.rb +1 -2
- data/lib/volt/utils/promise_extensions.rb +1 -1
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +0 -2
- data/lib/volt/volt/client_setup/browser.rb +11 -0
- data/spec/apps/kitchen_sink/Gemfile +37 -14
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
- data/spec/apps/kitchen_sink/app/main/controllers/events_controller.rb +26 -0
- data/spec/apps/kitchen_sink/app/main/views/events/index.html +30 -0
- data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +3 -0
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +1 -6
- data/spec/apps/kitchen_sink/app/main/views/{yield-component → yield_component}/index.html +0 -0
- data/spec/extra_core/array_spec.rb +26 -0
- data/spec/integration/bindings_spec.rb +9 -0
- data/spec/integration/event_spec.rb +19 -0
- data/spec/models/array_model_spec.rb +13 -0
- data/spec/models/field_helpers_spec.rb +2 -2
- data/spec/models/validations_spec.rb +31 -0
- data/spec/models/validators/type_validator_spec.rb +47 -1
- data/spec/reactive/reactive_array_spec.rb +46 -0
- data/spec/server/forking_server_spec.rb +27 -0
- data/spec/server/html_parser/view_scope_spec.rb +44 -0
- data/spec/server/rack/asset_files_spec.rb +2 -2
- data/templates/project/Gemfile.tt +8 -0
- data/templates/project/config/app.rb.tt +2 -1
- data/volt.gemspec +1 -1
- metadata +31 -5
@@ -8,4 +8,26 @@ class Array
|
|
8
8
|
def to_h
|
9
9
|
Hash[self]
|
10
10
|
end
|
11
|
+
|
12
|
+
|
13
|
+
# Converts an array to a sentence
|
14
|
+
def to_sentence(options={})
|
15
|
+
conjunction = options.fetch(:conjunction, 'and')
|
16
|
+
comma = options.fetch(:comma, ',')
|
17
|
+
oxford = options.fetch(:oxford, true) # <- true is the right value
|
18
|
+
|
19
|
+
case size
|
20
|
+
when 0
|
21
|
+
''
|
22
|
+
when 1
|
23
|
+
self[0].to_s
|
24
|
+
when 2
|
25
|
+
self.join(" #{conjunction} ")
|
26
|
+
else
|
27
|
+
str = self[0..-2].join(comma + ' ')
|
28
|
+
str += comma if oxford
|
29
|
+
str += " #{conjunction} " + self[-1].to_s
|
30
|
+
str
|
31
|
+
end
|
32
|
+
end
|
11
33
|
end
|
@@ -241,6 +241,7 @@ module Volt
|
|
241
241
|
def new_model(*args)
|
242
242
|
Volt::Model.class_at_path(options[:path]).new(*args)
|
243
243
|
end
|
244
|
+
alias_method :new, :new_model
|
244
245
|
|
245
246
|
def new_array_model(*args)
|
246
247
|
Volt::ArrayModel.class_at_path(options[:path]).new(*args)
|
@@ -306,6 +307,11 @@ module Volt
|
|
306
307
|
name.pluralize
|
307
308
|
end
|
308
309
|
|
310
|
+
alias_method :reactive_count, :count
|
311
|
+
def count(&block)
|
312
|
+
all.reactive_count(&block)
|
313
|
+
end
|
314
|
+
|
309
315
|
private
|
310
316
|
# called form <<, append, and create. If a hash is passed in, it converts
|
311
317
|
# it to a model. Then it takes the model and inserts it into the ArrayModel
|
@@ -381,7 +387,7 @@ module Volt
|
|
381
387
|
end
|
382
388
|
|
383
389
|
# We need to setup the proxy methods below where they are defined.
|
384
|
-
proxy_with_load :[], :size, :last, :reverse, :all, :to_a, :empty?, :present?, :blank?
|
390
|
+
proxy_with_load :[], :size, :length, :last, :reverse, :all, :to_a, :empty?, :present?, :blank?
|
385
391
|
|
386
392
|
end
|
387
393
|
end
|
data/lib/volt/models/errors.rb
CHANGED
@@ -22,34 +22,45 @@ module FieldHelpers
|
|
22
22
|
end
|
23
23
|
|
24
24
|
FIELD_CASTS = {
|
25
|
-
String
|
26
|
-
Fixnum
|
27
|
-
Numeric
|
28
|
-
Float
|
29
|
-
Time
|
30
|
-
TrueClass
|
31
|
-
FalseClass
|
25
|
+
String => :to_s.to_proc,
|
26
|
+
Fixnum => lambda {|val| NUMERIC_CAST[:Integer, val] },
|
27
|
+
Numeric => lambda {|val| NUMERIC_CAST[:Float, val] },
|
28
|
+
Float => lambda {|val| NUMERIC_CAST[:Float, val] },
|
29
|
+
Time => nil,
|
30
|
+
TrueClass => nil,
|
31
|
+
FalseClass => nil,
|
32
|
+
NilClass => nil,
|
33
|
+
Volt::Boolean => nil
|
32
34
|
}
|
33
|
-
VALID_FIELD_CLASSES = FIELD_CASTS.keys
|
34
35
|
|
35
36
|
|
36
37
|
module ClassMethods
|
37
38
|
# field lets you declare your fields instead of using the underscore syntax.
|
38
39
|
# An optional class restriction can be passed in.
|
39
|
-
def field(name,
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
def field(name, klasses = nil, options = {})
|
41
|
+
if klasses
|
42
|
+
klasses = [klasses].flatten
|
43
|
+
|
44
|
+
unless klasses.any? {|kl| FIELD_CASTS.key?(kl) }
|
45
|
+
klass_names = FIELD_CASTS.keys.map(&:to_s).join(', ')
|
46
|
+
msg = "valid field types is currently limited to #{klass_names}, you passed: #{klasses.inspect}"
|
47
|
+
fail FieldHelpers::InvalidFieldClass, msg
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add NilClass as an allowed type unless allow_nil: false was passed.
|
51
|
+
unless options[:allow_nil] == false
|
52
|
+
klasses << NilClass
|
53
|
+
end
|
44
54
|
end
|
45
55
|
|
46
56
|
self.fields_data ||= {}
|
47
|
-
self.fields_data[name] = [
|
57
|
+
self.fields_data[name] = [klasses, options]
|
48
58
|
|
49
|
-
if
|
50
|
-
# Add type validation, execpt for String, since anything can be
|
51
|
-
|
52
|
-
|
59
|
+
if klasses
|
60
|
+
# Add type validation, execpt for String, since anything can be cast to
|
61
|
+
# a string.
|
62
|
+
unless klasses.include?(String)
|
63
|
+
validate name, type: klasses
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
@@ -59,10 +70,14 @@ module FieldHelpers
|
|
59
70
|
|
60
71
|
define_method(:"#{name}=") do |val|
|
61
72
|
# Check if the value assigned matches the class restriction
|
62
|
-
if
|
73
|
+
if klasses
|
63
74
|
# Cast to the right type
|
64
|
-
|
65
|
-
|
75
|
+
klasses.each do |kl|
|
76
|
+
if (func = FIELD_CASTS[kl])
|
77
|
+
# Cast on the first available caster
|
78
|
+
val = func[val]
|
79
|
+
break
|
80
|
+
end
|
66
81
|
end
|
67
82
|
end
|
68
83
|
|
data/lib/volt/models/model.rb
CHANGED
@@ -354,6 +354,22 @@ module Volt
|
|
354
354
|
to_h.to_json
|
355
355
|
end
|
356
356
|
|
357
|
+
# Update tries to update the model and returns
|
358
|
+
def update(attrs)
|
359
|
+
old_attrs = @attributes.dup
|
360
|
+
Model.no_change_tracking do
|
361
|
+
assign_all_attributes(attrs, false)
|
362
|
+
|
363
|
+
validate!.then do |errs|
|
364
|
+
if errs && errs.present?
|
365
|
+
# Revert wholesale
|
366
|
+
@attributes = old_attrs
|
367
|
+
Promise.new.resolve(errs)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
357
373
|
private
|
358
374
|
def run_initial_setup(initial_setup)
|
359
375
|
# Save the changes
|
@@ -42,12 +42,15 @@ module Volt
|
|
42
42
|
|
43
43
|
if run_in_actions.size == 0 || run_in_actions.include?(action)
|
44
44
|
@instance_validations = {}
|
45
|
+
@instance_custom_validations = []
|
45
46
|
|
46
47
|
instance_exec(action, &block)
|
47
48
|
|
48
49
|
result = run_validations(@instance_validations)
|
50
|
+
result.merge!(run_custom_validations(@instance_custom_validations))
|
49
51
|
|
50
52
|
@instance_validations = nil
|
53
|
+
@instance_custom_validations = nil
|
51
54
|
|
52
55
|
result
|
53
56
|
end
|
@@ -55,9 +58,19 @@ module Volt
|
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
# Called on the model inside of a validations block. Allows the user to
|
62
|
+
# control if validations should be run.
|
63
|
+
def validate(field_name = nil, options = nil, &block)
|
64
|
+
if block
|
65
|
+
# Setup a custom validation inside of the current validations block.
|
66
|
+
if field_name || options
|
67
|
+
fail 'validate should be passed a field name and options or a block, not both.'
|
68
|
+
end
|
69
|
+
@instance_custom_validations << block
|
70
|
+
else
|
71
|
+
@instance_validations[field_name] ||= {}
|
72
|
+
@instance_validations[field_name].merge!(options)
|
73
|
+
end
|
61
74
|
end
|
62
75
|
|
63
76
|
def self.included(base)
|
@@ -200,10 +213,12 @@ module Volt
|
|
200
213
|
promise
|
201
214
|
end
|
202
215
|
|
203
|
-
def run_custom_validations
|
216
|
+
def run_custom_validations(custom_validations = nil)
|
217
|
+
# Default to running the class level custom validations
|
218
|
+
custom_validations ||= self.class.custom_validations
|
219
|
+
|
204
220
|
promise = Promise.new.resolve(nil)
|
205
|
-
|
206
|
-
custom_validations = self.class.custom_validations
|
221
|
+
|
207
222
|
if custom_validations
|
208
223
|
custom_validations.each do |custom_validation|
|
209
224
|
# Add to the promise chain
|
@@ -1,17 +1,49 @@
|
|
1
1
|
# Enforces a type on a field. Typically setup from ```field :name, Type```
|
2
2
|
module Volt
|
3
|
+
# Volt::Boolean can be used if you want a boolean type
|
4
|
+
class Boolean
|
5
|
+
end
|
6
|
+
|
3
7
|
class TypeValidator
|
4
8
|
def self.validate(model, field_name, args)
|
5
9
|
errors = {}
|
6
10
|
value = model.get(field_name)
|
7
11
|
|
8
|
-
type_restriction = args.is_a?(Hash) ? args[:type] : args
|
12
|
+
type_restriction = args.is_a?(Hash) ? (args[:type] || args[:types]) : args
|
9
13
|
|
10
|
-
|
14
|
+
# Make into an array of 1 if its not already an array.
|
15
|
+
type_restrictions = [type_restriction].flatten
|
16
|
+
|
17
|
+
valid_type = false
|
18
|
+
type_restrictions.each do |type_rest|
|
19
|
+
if value.is_a?(type_rest)
|
20
|
+
valid_type = true
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
unless valid_type
|
11
26
|
if args.is_a?(Hash) && args[:message]
|
12
27
|
message = args[:message]
|
13
28
|
else
|
14
|
-
|
29
|
+
type_msgs = type_restrictions.map do |type_rest|
|
30
|
+
if [Fixnum, Float, Numeric].include?(type_rest)
|
31
|
+
"a number"
|
32
|
+
elsif type_rest == NilClass
|
33
|
+
# we don't mention the nil restriction
|
34
|
+
nil
|
35
|
+
elsif type_rest == Volt::Boolean
|
36
|
+
['true', 'false']
|
37
|
+
elsif type_rest == TrueClass
|
38
|
+
'true'
|
39
|
+
elsif type_rest == FalseClass
|
40
|
+
'false'
|
41
|
+
else
|
42
|
+
"a #{type_rest.to_s}"
|
43
|
+
end
|
44
|
+
end.flatten
|
45
|
+
|
46
|
+
message = "must be #{type_msgs.compact.to_sentence(conjunction: 'or')}"
|
15
47
|
end
|
16
48
|
|
17
49
|
errors[field_name] = [message]
|
@@ -5,6 +5,9 @@ module Volt
|
|
5
5
|
class JSEvent
|
6
6
|
attr_reader :js_event
|
7
7
|
|
8
|
+
# The Volt controller that dispatched the event.
|
9
|
+
attr_accessor :controller
|
10
|
+
|
8
11
|
def initialize(js_event)
|
9
12
|
@js_event = js_event
|
10
13
|
end
|
@@ -43,31 +46,52 @@ module Volt
|
|
43
46
|
end
|
44
47
|
|
45
48
|
|
46
|
-
handler = proc do |js_event|
|
49
|
+
handler = proc do |js_event, *args|
|
47
50
|
event = JSEvent.new(js_event)
|
48
51
|
event.prevent_default! if event_name == 'submit'
|
49
52
|
|
50
|
-
#
|
51
|
-
# pass
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
# When the event is triggered via ```trigger(..)``` in a controller,
|
54
|
+
# it will pass its self as the first argument. We set that to
|
55
|
+
# ```controller``` on the event, so it can be easily accessed.
|
56
|
+
if args[0].is_a?(Volt::ModelController)
|
57
|
+
args = args.dup
|
58
|
+
event.controller = args.shift
|
59
|
+
end
|
57
60
|
|
58
|
-
|
59
|
-
# # handled
|
60
|
-
# if result.is_a?(Promise) && !result.next
|
61
|
-
# result.fail do |err|
|
62
|
-
# Volt.logger.error("EventBinding Error: promise returned from event binding #{@event_name} was rejected")
|
63
|
-
# Volt.logger.error(err)
|
64
|
-
# end
|
65
|
-
# end
|
61
|
+
args << event
|
66
62
|
|
63
|
+
self.class.call_handler_proc(@context, call_proc, event, args)
|
67
64
|
end
|
65
|
+
|
68
66
|
@listener = browser.events.add(@event_name, self, handler)
|
69
67
|
end
|
70
68
|
|
69
|
+
def self.call_handler_proc(context, call_proc, event, args)
|
70
|
+
# When the EventBinding is compiled, it converts a passed in string to
|
71
|
+
# get a Method:
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
# <a e-awesome="some_method">...</a>
|
75
|
+
#
|
76
|
+
# The call_proc will be passed in as: Proc.new { method(:some_method) }
|
77
|
+
#
|
78
|
+
# So first we call the call_proc, then that returns a method (or proc),
|
79
|
+
# which we call passing in the arguments based on the arity.
|
80
|
+
#
|
81
|
+
# If the e- binding has arguments passed to it, we just use those.
|
82
|
+
result = context.instance_exec(event, &call_proc)
|
83
|
+
# Trim args to match arity
|
84
|
+
|
85
|
+
# The proc returned a
|
86
|
+
if result && result.is_a?(Method)
|
87
|
+
args = args[0...result.arity]
|
88
|
+
|
89
|
+
result.call(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
71
95
|
# Remove the event binding
|
72
96
|
def remove
|
73
97
|
browser.events.remove(@event_name, self)
|
@@ -14,10 +14,12 @@ module Volt
|
|
14
14
|
|
15
15
|
that = self
|
16
16
|
|
17
|
+
document_handler = proc do |*args|
|
18
|
+
handle(event, *args)
|
19
|
+
end
|
20
|
+
|
17
21
|
`
|
18
|
-
$('body').on(event,
|
19
|
-
that.$handle(event, e, e.target || e.originalEvent.target);
|
20
|
-
});
|
22
|
+
$('body').on(event, #{document_handler});
|
21
23
|
`
|
22
24
|
|
23
25
|
end
|
@@ -26,8 +28,8 @@ module Volt
|
|
26
28
|
@events[event][binding.binding_name][binding.object_id] = handler
|
27
29
|
end
|
28
30
|
|
29
|
-
def handle(event_name, event,
|
30
|
-
element = `$(
|
31
|
+
def handle(event_name, event, *args)
|
32
|
+
element = `$(event.target || event.originalEvent.target)`
|
31
33
|
|
32
34
|
loop do
|
33
35
|
# Lookup the handler, make sure to not assume the group
|
@@ -43,7 +45,7 @@ module Volt
|
|
43
45
|
if handlers
|
44
46
|
handlers.values.each do |handler|
|
45
47
|
# Call each handler for this object
|
46
|
-
handler.call(event)
|
48
|
+
handler.call(event, *args)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -93,10 +93,27 @@ module Volt
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
def last
|
97
|
+
self[-1]
|
98
|
+
end
|
99
|
+
|
96
100
|
# TODO: Handle a range
|
97
101
|
def [](index)
|
98
102
|
# Handle a negative index, depend on size
|
99
|
-
|
103
|
+
if index < 0
|
104
|
+
# Depend on size by calling .size, since we are looking up reverse
|
105
|
+
# indexes
|
106
|
+
|
107
|
+
# cache size lookup
|
108
|
+
size = self.size
|
109
|
+
|
110
|
+
index = size + index
|
111
|
+
|
112
|
+
# In this case, we're looking back past 0 (going backwards), so we get
|
113
|
+
# nil. Since we're depending on @size_dep (because we called .size),
|
114
|
+
# it will invalidate when the size changes.
|
115
|
+
return nil if index < 0
|
116
|
+
end
|
100
117
|
|
101
118
|
# Get or create the dependency
|
102
119
|
dep = (@array_deps[index] ||= Dependency.new)
|
@@ -257,8 +257,14 @@ module Volt
|
|
257
257
|
|
258
258
|
def start_change_listener
|
259
259
|
sync_mod_time
|
260
|
+
|
261
|
+
options = {}
|
262
|
+
if ENV['POLL_FS']
|
263
|
+
options[:force_polling] = true
|
264
|
+
end
|
265
|
+
|
260
266
|
# Setup the listeners for file changes
|
261
|
-
@listener = Listen.to("#{@server.app_path}/") do |modified, added, removed|
|
267
|
+
@listener = Listen.to("#{@server.app_path}/", options) do |modified, added, removed|
|
262
268
|
Thread.new do
|
263
269
|
# Run the reload in a new thread
|
264
270
|
reload(modified + added + removed)
|