volt 0.8.27.beta3 → 0.8.27.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +3 -2
- data/{Readme.md → README.md} +9 -12
- data/Rakefile +2 -9
- data/VERSION +1 -1
- data/app/volt/models/user.rb +8 -0
- data/app/volt/tasks/live_query/data_store.rb +13 -5
- data/app/volt/tasks/live_query/live_query.rb +45 -3
- data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
- data/app/volt/tasks/query_tasks.rb +20 -2
- data/app/volt/tasks/store_tasks.rb +37 -20
- data/app/volt/tasks/user_tasks.rb +15 -13
- data/lib/volt/boot.rb +5 -3
- data/lib/volt/cli/console.rb +1 -0
- data/lib/volt/cli/generate.rb +15 -0
- data/lib/volt/cli.rb +19 -12
- data/lib/volt/config.rb +1 -1
- data/lib/volt/controllers/model_controller.rb +13 -3
- data/lib/volt/extra_core/extra_core.rb +1 -0
- data/lib/volt/extra_core/hash.rb +26 -0
- data/lib/volt/extra_core/object.rb +5 -1
- data/lib/volt/models/array_model.rb +86 -35
- data/lib/volt/models/associations.rb +53 -0
- data/lib/volt/models/buffer.rb +22 -10
- data/lib/volt/models/dirty.rb +88 -0
- data/lib/volt/models/errors.rb +21 -0
- data/lib/volt/models/field_helpers.rb +2 -2
- data/lib/volt/models/listener_tracker.rb +17 -0
- data/lib/volt/models/model.rb +213 -69
- data/lib/volt/models/model_helpers.rb +27 -17
- data/lib/volt/models/permissions.rb +246 -0
- data/lib/volt/models/persistors/array_store.rb +149 -81
- data/lib/volt/models/persistors/base.rb +16 -0
- data/lib/volt/models/persistors/cookies.rb +14 -9
- data/lib/volt/models/persistors/flash.rb +3 -0
- data/lib/volt/models/persistors/local_store.rb +0 -16
- data/lib/volt/models/persistors/model_store.rb +1 -2
- data/lib/volt/models/persistors/query/normalizer.rb +51 -0
- data/lib/volt/models/persistors/query/query_listener.rb +21 -5
- data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
- data/lib/volt/models/persistors/store.rb +8 -0
- data/lib/volt/models/persistors/store_state.rb +4 -27
- data/lib/volt/models/state_helpers.rb +11 -0
- data/lib/volt/models/state_manager.rb +43 -0
- data/lib/volt/models/url.rb +5 -5
- data/lib/volt/models/validations.rb +38 -41
- data/lib/volt/models/validators/email_validator.rb +4 -9
- data/lib/volt/models/validators/format_validator.rb +23 -8
- data/lib/volt/models/validators/length_validator.rb +2 -2
- data/lib/volt/models/validators/numericality_validator.rb +7 -3
- data/lib/volt/models/validators/phone_number_validator.rb +4 -9
- data/lib/volt/models/validators/presence_validator.rb +2 -2
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/models/validators/user_validation.rb +6 -0
- data/lib/volt/models.rb +8 -3
- data/lib/volt/page/bindings/attribute_binding.rb +10 -4
- data/lib/volt/page/bindings/content_binding.rb +9 -5
- data/lib/volt/page/bindings/if_binding.rb +25 -2
- data/lib/volt/page/bindings/template_binding.rb +19 -1
- data/lib/volt/page/bindings/yield_binding.rb +31 -0
- data/lib/volt/page/page.rb +11 -16
- data/lib/volt/reactive/class_eventable.rb +71 -0
- data/lib/volt/reactive/computation.rb +79 -10
- data/lib/volt/reactive/dependency.rb +27 -8
- data/lib/volt/reactive/eventable.rb +36 -22
- data/lib/volt/reactive/reactive_array.rb +2 -3
- data/lib/volt/reactive/reactive_hash.rb +8 -3
- data/lib/volt/router/routes.rb +2 -1
- data/lib/volt/server/component_templates.rb +0 -2
- data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
- data/lib/volt/server/html_parser/view_handler.rb +3 -0
- data/lib/volt/server/html_parser/view_parser.rb +1 -0
- data/lib/volt/server/html_parser/view_scope.rb +17 -41
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/rack/index_files.rb +9 -4
- data/lib/volt/server/rack/opal_files.rb +22 -14
- data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
- data/lib/volt/server/socket_connection_handler.rb +4 -0
- data/lib/volt/spec/setup.rb +26 -0
- data/lib/volt/tasks/dispatcher.rb +11 -0
- data/lib/volt/utils/event_counter.rb +29 -0
- data/lib/volt/utils/generic_pool.rb +12 -0
- data/lib/volt/utils/modes.rb +40 -0
- data/lib/volt/utils/promise_patch.rb +66 -0
- data/lib/volt/utils/timers.rb +33 -0
- data/lib/volt/volt/users.rb +48 -5
- data/lib/volt.rb +4 -0
- data/spec/apps/kitchen_sink/Gemfile +3 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
- data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
- data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
- data/spec/extra_core/logger_spec.rb +4 -2
- data/spec/integration/user_spec.rb +42 -42
- data/spec/integration/yield_spec.rb +18 -0
- data/spec/models/associations_spec.rb +37 -0
- data/spec/models/dirty_spec.rb +102 -0
- data/spec/models/model_spec.rb +64 -8
- data/spec/models/model_state_spec.rb +24 -0
- data/spec/models/permissions_spec.rb +96 -0
- data/spec/models/user_spec.rb +8 -5
- data/spec/models/user_validation_spec.rb +24 -0
- data/spec/models/validations_spec.rb +44 -5
- data/spec/models/validators/email_validator_spec.rb +109 -82
- data/spec/models/validators/format_validator_spec.rb +4 -107
- data/spec/models/validators/length_validator_spec.rb +9 -9
- data/spec/models/validators/phone_number_validator_spec.rb +60 -103
- data/spec/models/validators/shared_examples_for_validators.rb +123 -0
- data/spec/reactive/class_eventable_spec.rb +37 -0
- data/spec/reactive/computation_spec.rb +68 -3
- data/spec/reactive/dependency_spec.rb +71 -0
- data/spec/reactive/eventable_spec.rb +21 -0
- data/spec/reactive/reactive_hash_spec.rb +12 -0
- data/spec/router/routes_spec.rb +50 -50
- data/spec/server/html_parser/view_parser_spec.rb +0 -3
- data/spec/server/rack/component_paths_spec.rb +11 -0
- data/spec/server/rack/quite_common_logger_spec.rb +3 -4
- data/spec/spec_helper.rb +7 -3
- data/templates/component/config/dependencies.rb +1 -7
- data/templates/component/config/routes.rb +1 -1
- data/templates/component/controllers/main_controller.rb.tt +20 -0
- data/templates/component/views/{index → main}/index.html.tt +0 -0
- data/templates/newgem/lib/newgem.rb.tt +1 -3
- data/templates/project/app/main/config/routes.rb +3 -3
- data/templates/project/app/main/views/main/main.html.tt +4 -4
- data/templates/project/config/app.rb.tt +6 -0
- data/volt.gemspec +11 -7
- metadata +96 -42
- data/lib/volt/models/model_state.rb +0 -21
- data/templates/component/controllers/main_controller.rb +0 -18
@@ -143,7 +143,7 @@ module Volt
|
|
143
143
|
# Update the models based on the id/identity map. Usually these requests
|
144
144
|
# will come from the backend.
|
145
145
|
def self.changed(model_id, data)
|
146
|
-
Model.
|
146
|
+
Model.no_save do
|
147
147
|
model = @@identity_map.lookup(model_id)
|
148
148
|
|
149
149
|
if model
|
@@ -203,7 +203,6 @@ module Volt
|
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
206
|
-
# puts "Update Collection: #{collection.inspect} - #{values.inspect} -- #{Thread.current['in_channel'].inspect}"
|
207
206
|
QueryTasks.live_query_pool.updated_collection(collection.to_s, Thread.current['in_channel'])
|
208
207
|
{}
|
209
208
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Volt
|
2
|
+
module Query
|
3
|
+
# Normalizes queries so queries that are the same have the same order and parts
|
4
|
+
class Normalizer
|
5
|
+
def self.normalize(query)
|
6
|
+
query = merge_finds_and_move_to_front(query)
|
7
|
+
|
8
|
+
query = reject_skip_zero(query)
|
9
|
+
|
10
|
+
query
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.merge_finds_and_move_to_front(query)
|
14
|
+
# Map first parts to string
|
15
|
+
query = query.map {|v| v[0] = v[0].to_s ; v }
|
16
|
+
has_find = query.find {|v| v[0] == 'find' }
|
17
|
+
|
18
|
+
if has_find
|
19
|
+
# merge any finds
|
20
|
+
merged_find_query = {}
|
21
|
+
query = query.reject do |query_part|
|
22
|
+
if query_part[0] == 'find'
|
23
|
+
# on a find, merge into finds
|
24
|
+
find_query = query_part[1]
|
25
|
+
merged_find_query.merge!(find_query) if find_query
|
26
|
+
|
27
|
+
# reject
|
28
|
+
true
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add finds to the front
|
35
|
+
query.insert(0, ['find', merged_find_query])
|
36
|
+
else
|
37
|
+
# No find was done, add it in the first position
|
38
|
+
query.insert(0, ['find'])
|
39
|
+
end
|
40
|
+
|
41
|
+
query
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.reject_skip_zero(query)
|
45
|
+
query.reject do |query_part|
|
46
|
+
query_part[0] == 'skip' && query_part[1] == 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -3,6 +3,8 @@ module Volt
|
|
3
3
|
# a query have changed. It then will make the necessary changes to any ArrayStore's
|
4
4
|
# to get them to display the new data.
|
5
5
|
class QueryListener
|
6
|
+
attr_reader :listening
|
7
|
+
|
6
8
|
def initialize(query_listener_pool, tasks, collection, query)
|
7
9
|
@query_listener_pool = query_listener_pool
|
8
10
|
@tasks = tasks
|
@@ -30,25 +32,39 @@ module Volt
|
|
30
32
|
store.add(index, data)
|
31
33
|
end
|
32
34
|
|
33
|
-
store.change_state_to(:loaded)
|
35
|
+
store.model.change_state_to(:loaded_state, :loaded)
|
36
|
+
|
37
|
+
# if Volt.server?
|
38
|
+
# puts "BACK TO DIRTY"
|
39
|
+
# store.model.change_state_to(:loaded_state, :dirty)
|
40
|
+
# end
|
34
41
|
end
|
35
42
|
end.fail do |err|
|
36
|
-
|
43
|
+
# TODO: need to make it so we can re-raise out of this promise
|
44
|
+
Volt.logger.error("Error adding listener: #{err.inspect}")
|
45
|
+
Volt.logger.error(err.backtrace)
|
46
|
+
|
47
|
+
raise err
|
37
48
|
end
|
38
49
|
end
|
39
50
|
|
40
51
|
def add_store(store, &block)
|
41
52
|
@stores << store
|
42
53
|
|
54
|
+
# puts "ADD STORE FOR: #{@collection.inspect}: #{@query.inspect}"
|
55
|
+
|
43
56
|
if @listening
|
44
57
|
# We are already listening and have this model somewhere else,
|
45
58
|
# copy the data from the existing model.
|
46
59
|
store.model.clear
|
47
|
-
|
60
|
+
|
61
|
+
# Get an existing store to copy data from
|
62
|
+
first_store_model = @stores.first.model
|
63
|
+
first_store_model.each_with_index do |item, index|
|
48
64
|
store.add(index, item.to_h)
|
49
65
|
end
|
50
66
|
|
51
|
-
store.change_state_to(:
|
67
|
+
store.model.change_state_to(:loaded_state, first_store_model.loaded_state)
|
52
68
|
else
|
53
69
|
# First time we've added a store, setup the listener and get
|
54
70
|
# the initial data.
|
@@ -59,6 +75,7 @@ module Volt
|
|
59
75
|
def remove_store(store)
|
60
76
|
@stores.delete(store)
|
61
77
|
|
78
|
+
# puts "REMOVE STORE: #{@collection.inspect}: #{@query.inspect} - #{@stores.size}"
|
62
79
|
# When there are no stores left, remove the query listener from
|
63
80
|
# the pool, it can get created again later.
|
64
81
|
if @stores.size == 0
|
@@ -86,7 +103,6 @@ module Volt
|
|
86
103
|
|
87
104
|
def changed(model_id, data)
|
88
105
|
$loading_models = true
|
89
|
-
puts "new data: #{data.inspect}"
|
90
106
|
Persistors::ModelStore.changed(model_id, data)
|
91
107
|
$loading_models = false
|
92
108
|
end
|
@@ -7,14 +7,5 @@ module Volt
|
|
7
7
|
# query in different places. This makes it so we only need to track a
|
8
8
|
# single query at once. Data updates will only be sent once as well.
|
9
9
|
class QueryListenerPool < GenericPool
|
10
|
-
def print
|
11
|
-
puts '--- Running Queries ---'
|
12
|
-
|
13
|
-
@pool.each_pair do |table, query_hash|
|
14
|
-
query_hash.each_key do |query|
|
15
|
-
puts "#{table}: #{query.inspect}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
10
|
end
|
20
11
|
end
|
@@ -4,33 +4,10 @@ module Volt
|
|
4
4
|
module StoreState
|
5
5
|
# Called when a collection loads
|
6
6
|
def loaded(initial_state = nil)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@state_dep ||= Dependency.new
|
12
|
-
@state_dep.depend
|
13
|
-
|
14
|
-
@state
|
15
|
-
end
|
16
|
-
|
17
|
-
# Called from the QueryListener when the data is loaded
|
18
|
-
def change_state_to(new_state)
|
19
|
-
old_state = @state
|
20
|
-
@state = new_state
|
21
|
-
|
22
|
-
# Trigger changed on the 'state' method
|
23
|
-
if old_state != @state
|
24
|
-
@state_dep.changed! if @state_dep
|
25
|
-
end
|
26
|
-
|
27
|
-
if @state == :loaded && @fetch_promises
|
28
|
-
# Trigger each waiting fetch
|
29
|
-
@fetch_promises.compact.each { |fp| fp.resolve(@model) }
|
30
|
-
@fetch_promises = nil
|
31
|
-
|
32
|
-
# puts "STOP LIST---------"
|
33
|
-
stop_listening
|
7
|
+
if initial_state
|
8
|
+
@model.change_state_to(:loaded_state, initial_state)
|
9
|
+
elsif !@loaded_state
|
10
|
+
@model.change_state_to(:loaded_state, :not_loaded)
|
34
11
|
end
|
35
12
|
end
|
36
13
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Volt
|
2
|
+
module StateManager
|
3
|
+
def state_for(state_name)
|
4
|
+
ivar_name = :"@#{state_name}"
|
5
|
+
|
6
|
+
# Depend on the dep
|
7
|
+
state_dep_for(state_name).depend
|
8
|
+
|
9
|
+
instance_variable_get(ivar_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Called from the QueryListener when the data is loaded
|
13
|
+
def change_state_to(state_name, new_state, trigger=true)
|
14
|
+
# use an instance variable for the state storage
|
15
|
+
ivar_name = :"@#{state_name}"
|
16
|
+
|
17
|
+
old_state = instance_variable_get(ivar_name)
|
18
|
+
instance_variable_set(ivar_name, new_state)
|
19
|
+
|
20
|
+
# Trigger changed on the 'state' method
|
21
|
+
if old_state != new_state && trigger
|
22
|
+
dep = state_dep_for(state_name, false)
|
23
|
+
dep.changed! if dep
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
# Get a state ivar for state_name
|
29
|
+
# @params [String] the name of the state variable
|
30
|
+
# @params [Boolean] if true, one will be created if it does not exist
|
31
|
+
def state_dep_for(state_name, create=true)
|
32
|
+
dep_ivar_name = :"@#{state_name}_dep"
|
33
|
+
dep = instance_variable_get(dep_ivar_name)
|
34
|
+
if !dep && create
|
35
|
+
dep = Dependency.new
|
36
|
+
instance_variable_set(dep_ivar_name, dep)
|
37
|
+
end
|
38
|
+
|
39
|
+
dep
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/volt/models/url.rb
CHANGED
@@ -74,7 +74,7 @@ module Volt
|
|
74
74
|
nested_params_hash(params).each_pair do |key, value|
|
75
75
|
# remove the _ from the front
|
76
76
|
value = `encodeURI(value)`
|
77
|
-
query_parts << "#{key
|
77
|
+
query_parts << "#{key}=#{value}"
|
78
78
|
end
|
79
79
|
|
80
80
|
if query_parts.size > 0
|
@@ -176,7 +176,7 @@ module Volt
|
|
176
176
|
else
|
177
177
|
# assign value
|
178
178
|
if old_val != new_val
|
179
|
-
params.
|
179
|
+
params.set(name, new_val)
|
180
180
|
end
|
181
181
|
new_params.delete(name)
|
182
182
|
end
|
@@ -189,10 +189,10 @@ module Volt
|
|
189
189
|
def assign_new(params, new_params)
|
190
190
|
new_params.each_pair do |name, value|
|
191
191
|
if value.is_a?(Hash)
|
192
|
-
assign_new(params.
|
192
|
+
assign_new(params.get(name), value)
|
193
193
|
else
|
194
194
|
# assign
|
195
|
-
params.
|
195
|
+
params.set(name, value)
|
196
196
|
end
|
197
197
|
end
|
198
198
|
end
|
@@ -230,7 +230,7 @@ module Volt
|
|
230
230
|
# Example:
|
231
231
|
# user[name]=Ryan would parse as [:_user, :_name]
|
232
232
|
def query_key_sections(key)
|
233
|
-
key.split(/\[([^\]]+)\]/).reject(&:empty?)
|
233
|
+
key.split(/\[([^\]]+)\]/).reject(&:empty?)
|
234
234
|
end
|
235
235
|
|
236
236
|
# Generate the key for a nested param attribute
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
require 'volt/models/validators/email_validator'
|
1
|
+
require 'volt/models/errors'
|
3
2
|
require 'volt/models/validators/format_validator'
|
3
|
+
require 'volt/models/validators/email_validator'
|
4
4
|
require 'volt/models/validators/length_validator'
|
5
5
|
require 'volt/models/validators/numericality_validator'
|
6
6
|
require 'volt/models/validators/phone_number_validator'
|
@@ -66,57 +66,64 @@ module Volt
|
|
66
66
|
@server_errors.delete(key)
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
def errors(marked_only = false)
|
72
|
-
errors = {}
|
73
|
-
|
74
|
-
# Merge into errors, combining any error arrays
|
75
|
-
merge = proc do |new_errors|
|
76
|
-
errors.merge!(new_errors) do |key, new_val, old_val|
|
77
|
-
new_val + old_val
|
78
|
-
end
|
79
|
-
end
|
69
|
+
def errors(marked_only=false)
|
70
|
+
@errors ||= Errors.new
|
80
71
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
old_model = save_to
|
72
|
+
if marked_only
|
73
|
+
# Only return the fields that have been marked
|
74
|
+
@errors.to_h.select {|key,_| marked_fields[key] }
|
85
75
|
else
|
86
|
-
|
76
|
+
@errors
|
87
77
|
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO: Errors is being called for any validation change. We should have errors return a
|
81
|
+
# hash like object that only calls the validation for each one.
|
82
|
+
def validate!
|
83
|
+
errors.clear
|
88
84
|
|
89
|
-
|
85
|
+
run_validations
|
90
86
|
|
91
87
|
# See if any server errors are in place and merge them in if they are
|
92
88
|
if Volt.client?
|
93
|
-
errors
|
89
|
+
errors.merge!(server_errors.to_h)
|
94
90
|
end
|
95
91
|
|
96
|
-
|
92
|
+
run_custom_validations
|
97
93
|
|
98
94
|
errors
|
99
95
|
end
|
100
96
|
|
97
|
+
# Returns true if any of the changed fields now has an error
|
98
|
+
# @return [Boolean] true if one of the changed fields has an error.
|
99
|
+
def error_in_changed_attributes?
|
100
|
+
errs = errors
|
101
|
+
changed_attributes.each_pair do |key, _|
|
102
|
+
# If any of the fields with errors are also the ones that were
|
103
|
+
return true if errs[key]
|
104
|
+
end
|
105
|
+
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
|
101
110
|
private
|
102
111
|
|
103
112
|
# Runs through each of the normal validations.
|
104
|
-
def run_validations
|
113
|
+
def run_validations
|
105
114
|
validations = self.class.validations
|
106
115
|
if validations
|
107
116
|
|
108
117
|
# Run through each validation
|
109
118
|
validations.each_pair do |field_name, options|
|
110
|
-
# When marked only, skip any validations on non-marked fields
|
111
|
-
next if marked_only && !marked_fields[field_name]
|
112
|
-
|
113
119
|
options.each_pair do |validation, args|
|
114
120
|
# Call the specific validator, then merge the results back
|
115
121
|
# into one large errors hash.
|
116
122
|
klass = validation_class(validation, args)
|
117
123
|
|
118
124
|
if klass
|
119
|
-
|
125
|
+
result = klass.validate(self, field_name, args)
|
126
|
+
errors.merge!(result)
|
120
127
|
else
|
121
128
|
fail "validation type #{validation} is not specified."
|
122
129
|
end
|
@@ -127,27 +134,16 @@ module Volt
|
|
127
134
|
errors
|
128
135
|
end
|
129
136
|
|
130
|
-
def run_custom_validations
|
137
|
+
def run_custom_validations
|
131
138
|
# Call all of the custom validations
|
132
139
|
custom_validations = self.class.custom_validations
|
133
140
|
if custom_validations
|
134
141
|
custom_validations.each do |custom_validation|
|
135
|
-
# Run the validator in the context of the model
|
136
|
-
|
137
|
-
result
|
138
|
-
|
139
|
-
if result
|
140
|
-
errors = merge.call(result)
|
141
|
-
end
|
142
|
+
# Run the validator in the context of the model
|
143
|
+
result = instance_exec(&custom_validation)
|
144
|
+
errors.merge!(result)
|
142
145
|
end
|
143
146
|
end
|
144
|
-
|
145
|
-
errors
|
146
|
-
end
|
147
|
-
|
148
|
-
# calls the validate method on the class, passing the right arguments.
|
149
|
-
def validate_with(merge, klass, old_model, field_name, args)
|
150
|
-
merge.call(klass.validate(self, old_model, field_name, args))
|
151
147
|
end
|
152
148
|
|
153
149
|
def validation_class(validation, args)
|
@@ -155,5 +151,6 @@ module Volt
|
|
155
151
|
rescue NameError => e
|
156
152
|
puts "Unable to find #{validation} validator"
|
157
153
|
end
|
154
|
+
|
158
155
|
end
|
159
156
|
end
|
@@ -1,19 +1,14 @@
|
|
1
1
|
module Volt
|
2
|
-
class EmailValidator
|
2
|
+
class EmailValidator < FormatValidator
|
3
3
|
DEFAULT_OPTIONS = {
|
4
4
|
with: /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i,
|
5
5
|
message: 'must be an email address'
|
6
6
|
}
|
7
7
|
|
8
|
-
|
9
|
-
new(model, field_name, options).errors
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.new(model, field_name, options)
|
13
|
-
options = DEFAULT_OPTIONS if options == true
|
14
|
-
options = DEFAULT_OPTIONS.merge options
|
8
|
+
private
|
15
9
|
|
16
|
-
|
10
|
+
def default_options
|
11
|
+
DEFAULT_OPTIONS
|
17
12
|
end
|
18
13
|
end
|
19
14
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
module Volt
|
2
|
+
# Validates the format of a field against any number of block or regex
|
3
|
+
# criteria
|
2
4
|
class FormatValidator
|
3
5
|
# Creates a new instance with the provided options and returns it's errors
|
4
6
|
#
|
5
|
-
# @note the second param +old_model+ is unused and will soon be removed,
|
6
|
-
# you can pass nil in the mean time
|
7
|
-
#
|
8
7
|
# @example
|
9
8
|
# options = { with: /.+@.+/, message: 'must include an @ symobl' }
|
10
9
|
#
|
11
|
-
# FormatValidator.validate(user,
|
10
|
+
# FormatValidator.validate(user, 'email', options)
|
12
11
|
#
|
13
12
|
# @example
|
14
13
|
# numbers_only = /^\d+$/
|
@@ -19,17 +18,16 @@ module Volt
|
|
19
18
|
# { with: sum_equals_ten, message: 'must add up to 10' }
|
20
19
|
# ]
|
21
20
|
#
|
22
|
-
# FormatValidator.validate(user,
|
21
|
+
# FormatValidator.validate(user, 'email', options)
|
23
22
|
#
|
24
23
|
# @param model [Volt::Model] the model being validated
|
25
|
-
# @param old_model [NilClass] no longer used, will be removed
|
26
24
|
# @param field_name [String] the name of the field being validated
|
27
25
|
#
|
28
26
|
# @param options (see #apply)
|
29
27
|
# @option options (see #apply)
|
30
28
|
#
|
31
29
|
# @return (see #errors)
|
32
|
-
def self.validate(model,
|
30
|
+
def self.validate(model, field_name, options)
|
33
31
|
new(model, field_name).apply(options).errors
|
34
32
|
end
|
35
33
|
|
@@ -37,7 +35,8 @@ module Volt
|
|
37
35
|
# @param field_name [String] the name of the field being validated
|
38
36
|
def initialize(model, field_name)
|
39
37
|
@name = field_name
|
40
|
-
@value = model.
|
38
|
+
@value = model.get field_name
|
39
|
+
|
41
40
|
@criteria = []
|
42
41
|
end
|
43
42
|
|
@@ -57,6 +56,18 @@ module Volt
|
|
57
56
|
# @return [self] returns itself for chaining
|
58
57
|
def apply(options)
|
59
58
|
return apply_list options if options.is_a? Array
|
59
|
+
|
60
|
+
options = case options
|
61
|
+
when true
|
62
|
+
default_options
|
63
|
+
when Hash
|
64
|
+
if default_options.is_a? Hash
|
65
|
+
default_options.merge options
|
66
|
+
else
|
67
|
+
options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
60
71
|
with options[:with], options[:message]
|
61
72
|
self
|
62
73
|
end
|
@@ -107,6 +118,10 @@ module Volt
|
|
107
118
|
self
|
108
119
|
end
|
109
120
|
|
121
|
+
def default_options
|
122
|
+
{}
|
123
|
+
end
|
124
|
+
|
110
125
|
def test(criterion)
|
111
126
|
return false unless @value.respond_to? :match
|
112
127
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Volt
|
2
2
|
class LengthValidator
|
3
|
-
def self.validate(model,
|
3
|
+
def self.validate(model, field_name, args)
|
4
4
|
errors = {}
|
5
|
-
value = model.
|
5
|
+
value = model.get(field_name)
|
6
6
|
|
7
7
|
if args.is_a?(Fixnum)
|
8
8
|
min = args
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Volt
|
2
2
|
class NumericalityValidator
|
3
|
-
def self.validate(model,
|
3
|
+
def self.validate(model, field_name, args)
|
4
4
|
# Construct the class and return the errors
|
5
5
|
new(model, field_name, args).errors
|
6
6
|
end
|
@@ -12,10 +12,14 @@ module Volt
|
|
12
12
|
@args = args
|
13
13
|
@errors = {}
|
14
14
|
|
15
|
-
@value = model.
|
15
|
+
@value = model.get(field_name)
|
16
16
|
|
17
17
|
# Convert to float if it is a string for a float
|
18
|
-
|
18
|
+
# The nil check and the nan? check are only require for opal 0.6
|
19
|
+
unless @value.nil?
|
20
|
+
@value = Kernel.Float(@value) rescue nil
|
21
|
+
@value = nil if RUBY_PLATFORM == 'opal' && @value.nan?
|
22
|
+
end
|
19
23
|
|
20
24
|
check_errors
|
21
25
|
end
|
@@ -1,19 +1,14 @@
|
|
1
1
|
module Volt
|
2
|
-
class PhoneNumberValidator
|
2
|
+
class PhoneNumberValidator < FormatValidator
|
3
3
|
DEFAULT_OPTIONS = {
|
4
4
|
with: /^(\+?\d{1,2}[\.\-\ ]?\d{3}|\(\d{3}\)|\d{3})[\.\-\ ]?\d{3,4}[\.\-\ ]?\d{4}$/,
|
5
5
|
message: 'must be a phone number with area or country code'
|
6
6
|
}
|
7
7
|
|
8
|
-
|
9
|
-
new(model, field_name, options).errors
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.new(model, field_name, options)
|
13
|
-
options = DEFAULT_OPTIONS if options == true
|
14
|
-
options = DEFAULT_OPTIONS.merge options
|
8
|
+
private
|
15
9
|
|
16
|
-
|
10
|
+
def default_options
|
11
|
+
DEFAULT_OPTIONS
|
17
12
|
end
|
18
13
|
end
|
19
14
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Volt
|
2
2
|
class PresenceValidator
|
3
|
-
def self.validate(model,
|
3
|
+
def self.validate(model, field_name, args)
|
4
4
|
errors = {}
|
5
|
-
value = model.
|
5
|
+
value = model.get(field_name)
|
6
6
|
if !value || value.blank?
|
7
7
|
if args.is_a?(Hash) && args[:message]
|
8
8
|
message = args[:message]
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Volt
|
2
2
|
class UniqueValidator
|
3
|
-
def self.validate(model,
|
3
|
+
def self.validate(model, field_name, args)
|
4
4
|
errors = {}
|
5
5
|
|
6
6
|
if RUBY_PLATFORM != 'opal'
|
7
7
|
if args
|
8
|
-
value = model.
|
8
|
+
value = model.get(field_name)
|
9
9
|
|
10
10
|
query = {}
|
11
11
|
# Check to see if any other documents have this value.
|
data/lib/volt/models.rb
CHANGED
@@ -11,9 +11,14 @@ end
|
|
11
11
|
require 'volt/models/persistors/flash'
|
12
12
|
require 'volt/models/persistors/local_store'
|
13
13
|
if RUBY_PLATFORM == 'opal'
|
14
|
-
require 'promise
|
14
|
+
require 'promise'
|
15
15
|
else
|
16
16
|
# Opal doesn't expose its promise library directly
|
17
|
-
|
18
|
-
require
|
17
|
+
# gem_dir = Gem::Specification.find_by_name('opal').gem_dir
|
18
|
+
require 'opal'
|
19
|
+
|
20
|
+
gem_dir = File.join(Opal.gem_dir, '..')
|
21
|
+
require(gem_dir + '/stdlib/promise')
|
19
22
|
end
|
23
|
+
# TODO: remove once https://github.com/opal/opal/pull/725 is released.
|
24
|
+
require 'volt/utils/promise_patch'
|