volt 0.8.27.beta2 → 0.8.27.beta3
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/.ruby-version +1 -1
- data/.travis.yml +3 -0
- data/CHANGELOG.md +7 -2
- data/CONTRIBUTING.md +133 -0
- data/Gemfile +0 -1
- data/Rakefile +11 -3
- data/Readme.md +4 -4
- data/VERSION +1 -1
- data/app/volt/models/user.rb +6 -3
- data/lib/volt/cli/console.rb +21 -12
- data/lib/volt/cli/runner.rb +1 -1
- data/lib/volt/cli.rb +5 -4
- data/lib/volt/config.rb +9 -11
- data/lib/volt/controllers/model_controller.rb +16 -0
- data/lib/volt/data_stores/data_store.rb +1 -1
- data/lib/volt/extra_core/array.rb +1 -6
- data/lib/volt/extra_core/blank.rb +1 -3
- data/lib/volt/extra_core/class.rb +1 -1
- data/lib/volt/extra_core/extra_core.rb +0 -1
- data/lib/volt/extra_core/logger.rb +78 -1
- data/lib/volt/extra_core/object.rb +4 -4
- data/lib/volt/models/array_model.rb +2 -3
- data/lib/volt/models/buffer.rb +1 -2
- data/lib/volt/models/field_helpers.rb +4 -5
- data/lib/volt/models/model.rb +27 -1
- data/lib/volt/models/model_hash_behaviour.rb +3 -4
- data/lib/volt/models/persistors/array_store.rb +6 -7
- data/lib/volt/models/persistors/cookies.rb +2 -2
- data/lib/volt/models/persistors/model_store.rb +5 -6
- data/lib/volt/models/validations.rb +5 -7
- data/lib/volt/models/validators/email_validator.rb +8 -29
- data/lib/volt/models/validators/format_validator.rb +116 -0
- data/lib/volt/models/validators/numericality_validator.rb +2 -2
- data/lib/volt/models/validators/phone_number_validator.rb +8 -29
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/page/bindings/content_binding.rb +1 -1
- data/lib/volt/page/bindings/each_binding.rb +1 -1
- data/lib/volt/page/bindings/template_binding/view_lookup_for_path.rb +92 -0
- data/lib/volt/page/bindings/template_binding.rb +10 -85
- data/lib/volt/page/channel.rb +0 -1
- data/lib/volt/page/page.rb +5 -7
- data/lib/volt/page/sub_context.rb +1 -1
- data/lib/volt/page/targets/base_section.rb +2 -2
- data/lib/volt/page/targets/helpers/comment_searchers.rb +2 -2
- data/lib/volt/reactive/reactive_accessors.rb +1 -1
- data/lib/volt/reactive/reactive_array.rb +6 -6
- data/lib/volt/router/routes.rb +4 -4
- data/lib/volt/server/rack/asset_files.rb +1 -2
- data/lib/volt/server/rack/component_code.rb +0 -2
- data/lib/volt/server/rack/component_paths.rb +2 -2
- data/lib/volt/server/rack/quiet_common_logger.rb +2 -2
- data/lib/volt/spec/capybara.rb +1 -1
- data/lib/volt/spec/sauce_labs.rb +6 -6
- data/lib/volt/spec/setup.rb +8 -7
- data/lib/volt/tasks/dispatcher.rb +12 -10
- data/lib/volt/tasks/task_handler.rb +1 -1
- data/lib/volt/utils/generic_pool.rb +2 -2
- data/lib/volt/volt/users.rb +7 -9
- data/lib/volt.rb +2 -4
- data/spec/apps/file_loading/app/missing_deps/config/dependencies.rb +1 -1
- data/spec/apps/kitchen_sink/Gemfile +2 -2
- data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +0 -1
- data/spec/apps/kitchen_sink/app/main/models/user.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -1
- data/spec/apps/kitchen_sink/config/app.rb +1 -1
- data/spec/extra_core/array_spec.rb +4 -2
- data/spec/extra_core/blank_spec.rb +11 -0
- data/spec/extra_core/class_spec.rb +2 -2
- data/spec/extra_core/logger_spec.rb +50 -0
- data/spec/extra_core/string_transformations_spec.rb +0 -1
- data/spec/integration/cookies_spec.rb +1 -2
- data/spec/integration/flash_spec.rb +2 -3
- data/spec/integration/list_spec.rb +1 -1
- data/spec/integration/templates_spec.rb +0 -1
- data/spec/integration/url_spec.rb +1 -2
- data/spec/integration/user_spec.rb +3 -3
- data/spec/models/field_helpers_spec.rb +2 -2
- data/spec/models/model_spec.rb +21 -2
- data/spec/models/user_spec.rb +69 -0
- data/spec/models/validations_spec.rb +69 -78
- data/spec/models/validators/email_validator_spec.rb +3 -3
- data/spec/models/validators/format_validator_spec.rb +144 -0
- data/spec/models/validators/length_validator_spec.rb +82 -0
- data/spec/models/validators/phone_number_validator_spec.rb +3 -3
- data/spec/page/bindings/template_binding/view_lookup_for_path_spec.rb +149 -0
- data/spec/page/bindings/template_binding_spec.rb +0 -151
- data/spec/reactive/computation_spec.rb +46 -0
- data/spec/reactive/dependency_spec.rb +0 -1
- data/spec/reactive/reactive_array_spec.rb +0 -1
- data/spec/router/routes_spec.rb +0 -4
- data/spec/server/html_parser/view_parser_spec.rb +0 -4
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/spec/server/rack/quite_common_logger_spec.rb +55 -0
- data/spec/spec_helper.rb +2 -5
- data/spec/tasks/dispatcher_spec.rb +16 -5
- data/spec/tasks/live_query_spec.rb +0 -1
- data/spec/tasks/query_tasks.rb +0 -1
- data/spec/tasks/query_tracker_spec.rb +0 -3
- data/spec/templates/targets/binding_document/component_node_spec.rb +0 -1
- data/spec/utils/generic_counting_pool_spec.rb +0 -1
- data/spec/utils/generic_pool_spec.rb +0 -1
- data/templates/component/assets/images/.empty_directory +0 -0
- data/templates/project/README.md.tt +3 -2
- data/templates/project/app/main/assets/images/.empty_directory +0 -0
- data/templates/project/config/base/index.html +6 -7
- data/volt.gemspec +3 -5
- metadata +27 -9
- data/lib/volt/extra_core/numeric.rb +0 -9
data/lib/volt/models/model.rb
CHANGED
|
@@ -12,6 +12,11 @@ module Volt
|
|
|
12
12
|
class NilMethodCall < NoMethodError
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# The error is raised when a reserved field name is used in a
|
|
16
|
+
# volt model.
|
|
17
|
+
class InvalidFieldName < StandardError
|
|
18
|
+
end
|
|
19
|
+
|
|
15
20
|
class Model
|
|
16
21
|
include ModelWrapper
|
|
17
22
|
include ModelHelpers
|
|
@@ -24,6 +29,14 @@ module Volt
|
|
|
24
29
|
attr_reader :attributes
|
|
25
30
|
attr_reader :parent, :path, :persistor, :options
|
|
26
31
|
|
|
32
|
+
INVALID_FIELD_NAMES = {
|
|
33
|
+
:attributes => true,
|
|
34
|
+
:parent => true,
|
|
35
|
+
:path => true,
|
|
36
|
+
:options => true,
|
|
37
|
+
:persistor => true
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
def initialize(attributes = {}, options = {}, initial_state = nil)
|
|
28
41
|
@deps = HashDependency.new
|
|
29
42
|
@size_dep = Dependency.new
|
|
@@ -64,7 +77,7 @@ module Volt
|
|
|
64
77
|
|
|
65
78
|
if attrs
|
|
66
79
|
# Assign id first
|
|
67
|
-
id
|
|
80
|
+
id = attrs.delete(:_id)
|
|
68
81
|
|
|
69
82
|
# When doing a mass-assign, we don't save until the end.
|
|
70
83
|
Model.nosave do
|
|
@@ -141,6 +154,8 @@ module Volt
|
|
|
141
154
|
# Assign, without the =
|
|
142
155
|
attribute_name = method_name.to_sym
|
|
143
156
|
|
|
157
|
+
check_valid_field_name(attribute_name)
|
|
158
|
+
|
|
144
159
|
value = args[0]
|
|
145
160
|
|
|
146
161
|
old_value = @attributes[attribute_name]
|
|
@@ -175,6 +190,8 @@ module Volt
|
|
|
175
190
|
# Reading an attribute, we may get back a nil model.
|
|
176
191
|
attr_name = attr_name.to_sym
|
|
177
192
|
|
|
193
|
+
check_valid_field_name(attr_name)
|
|
194
|
+
|
|
178
195
|
# Track dependency
|
|
179
196
|
# @deps.depend(attr_name)
|
|
180
197
|
|
|
@@ -293,6 +310,15 @@ module Volt
|
|
|
293
310
|
|
|
294
311
|
private
|
|
295
312
|
|
|
313
|
+
# Volt provides a few access methods to get more data about the model,
|
|
314
|
+
# we want to prevent these from being assigned or accessed through
|
|
315
|
+
# underscore methods.
|
|
316
|
+
def check_valid_field_name(name)
|
|
317
|
+
if INVALID_FIELD_NAMES[name]
|
|
318
|
+
raise InvalidFieldName, "`#{name}` is reserved and can not be used as a field"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
296
322
|
def setup_buffer(model)
|
|
297
323
|
model.attributes = attributes
|
|
298
324
|
model.change_state_to(:loaded)
|
|
@@ -29,7 +29,7 @@ module Volt
|
|
|
29
29
|
|
|
30
30
|
keys = []
|
|
31
31
|
|
|
32
|
-
each_pair do |k,v|
|
|
32
|
+
each_pair do |k, v|
|
|
33
33
|
keys << k
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -75,8 +75,8 @@ module Volt
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def each_pair
|
|
78
|
-
@attributes.each_pair do |k,v|
|
|
79
|
-
yield(k,v) unless v.is_a?(Model) && v.nil?
|
|
78
|
+
@attributes.each_pair do |k, v|
|
|
79
|
+
yield(k, v) unless v.is_a?(Model) && v.nil?
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -98,6 +98,5 @@ module Volt
|
|
|
98
98
|
hash
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
|
-
|
|
102
101
|
end
|
|
103
102
|
end
|
|
@@ -2,7 +2,6 @@ require 'volt/models/persistors/store'
|
|
|
2
2
|
require 'volt/models/persistors/query/query_listener_pool'
|
|
3
3
|
require 'volt/models/persistors/store_state'
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
module Volt
|
|
7
6
|
module Persistors
|
|
8
7
|
class ArrayStore < Store
|
|
@@ -83,12 +82,12 @@ module Volt
|
|
|
83
82
|
|
|
84
83
|
# Clear out the models data, since we're not listening anymore.
|
|
85
84
|
def unload_data
|
|
86
|
-
puts
|
|
85
|
+
puts 'Unload Data'
|
|
87
86
|
change_state_to :not_loaded
|
|
88
87
|
@model.clear
|
|
89
88
|
end
|
|
90
89
|
|
|
91
|
-
def run_query(model, query={}, skip=nil, limit=nil)
|
|
90
|
+
def run_query(model, query = {}, skip = nil, limit = nil)
|
|
92
91
|
@model.clear
|
|
93
92
|
|
|
94
93
|
collection = model.path.last
|
|
@@ -126,21 +125,21 @@ module Volt
|
|
|
126
125
|
query ||= {}
|
|
127
126
|
end
|
|
128
127
|
|
|
129
|
-
|
|
128
|
+
Cursor.new([], @model.options.merge(query: query))
|
|
130
129
|
end
|
|
131
130
|
|
|
132
131
|
def limit(limit)
|
|
133
|
-
|
|
132
|
+
Cursor.new([], @model.options.merge(limit: limit))
|
|
134
133
|
end
|
|
135
134
|
|
|
136
135
|
def skip(skip)
|
|
137
|
-
|
|
136
|
+
Cursor.new([], @model.options.merge(skip: skip))
|
|
138
137
|
end
|
|
139
138
|
|
|
140
139
|
# Returns a promise that is resolved/rejected when the query is complete. Any
|
|
141
140
|
# passed block will be passed to the promises then. Then will be passed the model.
|
|
142
141
|
def then(&block)
|
|
143
|
-
|
|
142
|
+
fail 'then must pass a block' unless block
|
|
144
143
|
promise = Promise.new
|
|
145
144
|
|
|
146
145
|
promise = promise.then(&block)
|
|
@@ -11,7 +11,7 @@ module Volt
|
|
|
11
11
|
cookies = `document.cookie`
|
|
12
12
|
Hash[cookies.split(';').map do |v|
|
|
13
13
|
# Equals are valid as part of a cookie, so only parse the first equals.
|
|
14
|
-
parts = v.split('=', 2).map { |p| p = p.strip
|
|
14
|
+
parts = v.split('=', 2).map { |p| p = p.strip; `decodeURIComponent(p)` }
|
|
15
15
|
|
|
16
16
|
# Default to empty if no value
|
|
17
17
|
parts << '' if parts.size == 1
|
|
@@ -21,7 +21,7 @@ module Volt
|
|
|
21
21
|
end]
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def write_cookie(key, value, options={})
|
|
24
|
+
def write_cookie(key, value, options = {})
|
|
25
25
|
parts = []
|
|
26
26
|
|
|
27
27
|
parts << `encodeURIComponent(key)`
|
|
@@ -62,9 +62,9 @@ module Volt
|
|
|
62
62
|
|
|
63
63
|
def save_changes?
|
|
64
64
|
if RUBY_PLATFORM == 'opal'
|
|
65
|
-
|
|
65
|
+
!(defined?($loading_models) && $loading_models) && @tasks
|
|
66
66
|
else
|
|
67
|
-
|
|
67
|
+
true
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -126,13 +126,12 @@ module Volt
|
|
|
126
126
|
StoreTasks.save(collection, @model.path, self_attributes).then do
|
|
127
127
|
save_promises = @save_promises
|
|
128
128
|
@save_promises = nil
|
|
129
|
-
save_promises.each {|promise| promise.resolve(nil) }
|
|
129
|
+
save_promises.each { |promise| promise.resolve(nil) }
|
|
130
130
|
end.fail do |errors|
|
|
131
131
|
save_promises = @save_promises
|
|
132
132
|
@save_promises = nil
|
|
133
|
-
save_promises.each {|promise| promise.reject(errors) }
|
|
133
|
+
save_promises.each { |promise| promise.reject(errors) }
|
|
134
134
|
end
|
|
135
|
-
|
|
136
135
|
end
|
|
137
136
|
|
|
138
137
|
def event_added(event, first, first_for_event)
|
|
@@ -206,7 +205,7 @@ module Volt
|
|
|
206
205
|
|
|
207
206
|
# puts "Update Collection: #{collection.inspect} - #{values.inspect} -- #{Thread.current['in_channel'].inspect}"
|
|
208
207
|
QueryTasks.live_query_pool.updated_collection(collection.to_s, Thread.current['in_channel'])
|
|
209
|
-
|
|
208
|
+
{}
|
|
210
209
|
end
|
|
211
210
|
end
|
|
212
211
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# require 'volt/models/validations/errors'
|
|
2
2
|
require 'volt/models/validators/email_validator'
|
|
3
|
+
require 'volt/models/validators/format_validator'
|
|
3
4
|
require 'volt/models/validators/length_validator'
|
|
4
5
|
require 'volt/models/validators/numericality_validator'
|
|
5
6
|
require 'volt/models/validators/phone_number_validator'
|
|
@@ -10,10 +11,10 @@ module Volt
|
|
|
10
11
|
# Include in any class to get validation logic
|
|
11
12
|
module Validations
|
|
12
13
|
module ClassMethods
|
|
13
|
-
def validate(field_name=nil, options=nil, &block)
|
|
14
|
+
def validate(field_name = nil, options = nil, &block)
|
|
14
15
|
if block
|
|
15
16
|
if field_name || options
|
|
16
|
-
|
|
17
|
+
fail 'validate should be passed a field name and options or a block, not both.'
|
|
17
18
|
end
|
|
18
19
|
self.custom_validations ||= []
|
|
19
20
|
custom_validations << block
|
|
@@ -39,7 +40,6 @@ module Volt
|
|
|
39
40
|
@marked_fields ||= ReactiveHash.new
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
|
|
43
43
|
# Marks all fields, useful for when a model saves.
|
|
44
44
|
def mark_all_fields!
|
|
45
45
|
validations = self.class.validations
|
|
@@ -50,7 +50,6 @@ module Volt
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
54
53
|
def marked_errors
|
|
55
54
|
errors(true)
|
|
56
55
|
end
|
|
@@ -125,7 +124,7 @@ module Volt
|
|
|
125
124
|
end
|
|
126
125
|
end
|
|
127
126
|
|
|
128
|
-
|
|
127
|
+
errors
|
|
129
128
|
end
|
|
130
129
|
|
|
131
130
|
def run_custom_validations(errors, merge, old_model)
|
|
@@ -143,10 +142,9 @@ module Volt
|
|
|
143
142
|
end
|
|
144
143
|
end
|
|
145
144
|
|
|
146
|
-
|
|
145
|
+
errors
|
|
147
146
|
end
|
|
148
147
|
|
|
149
|
-
|
|
150
148
|
# calls the validate method on the class, passing the right arguments.
|
|
151
149
|
def validate_with(merge, klass, old_model, field_name, args)
|
|
152
150
|
merge.call(klass.validate(self, old_model, field_name, args))
|
|
@@ -1,40 +1,19 @@
|
|
|
1
1
|
module Volt
|
|
2
2
|
class EmailValidator
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
DEFAULT_OPTIONS = {
|
|
4
|
+
with: /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i,
|
|
5
|
+
message: 'must be an email address'
|
|
6
|
+
}
|
|
5
7
|
|
|
6
8
|
def self.validate(model, old_model, field_name, options)
|
|
7
9
|
new(model, field_name, options).errors
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
+
def self.new(model, field_name, options)
|
|
13
|
+
options = DEFAULT_OPTIONS if options == true
|
|
14
|
+
options = DEFAULT_OPTIONS.merge options
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
when Hash, true, false
|
|
15
|
-
configure options
|
|
16
|
-
else
|
|
17
|
-
fail 'arguments can only be a Boolean or a Hash'
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def valid?
|
|
22
|
-
return false unless @value.is_a? String
|
|
23
|
-
|
|
24
|
-
!!@value.match(@custom_regex || DEFAULT_REGEX)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def errors
|
|
28
|
-
valid? ? {} : { email: [ @custom_message || ERROR_MESSAGE ] }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def configure(options)
|
|
34
|
-
return unless options.is_a? Hash
|
|
35
|
-
|
|
36
|
-
@custom_message = options.fetch(:error_message) { nil }
|
|
37
|
-
@custom_regex = options.fetch(:with) { nil }
|
|
16
|
+
FormatValidator.new(model, field_name).apply options
|
|
38
17
|
end
|
|
39
18
|
end
|
|
40
19
|
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Volt
|
|
2
|
+
class FormatValidator
|
|
3
|
+
# Creates a new instance with the provided options and returns it's errors
|
|
4
|
+
#
|
|
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
|
+
# @example
|
|
9
|
+
# options = { with: /.+@.+/, message: 'must include an @ symobl' }
|
|
10
|
+
#
|
|
11
|
+
# FormatValidator.validate(user, nil, 'email', options)
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# numbers_only = /^\d+$/
|
|
15
|
+
# sum_equals_ten = ->(s) { s.chars.map(&:to_i).reduce(:+) == 10 }
|
|
16
|
+
#
|
|
17
|
+
# options = [
|
|
18
|
+
# { with: numbers_only, message: 'must include only numbers' },
|
|
19
|
+
# { with: sum_equals_ten, message: 'must add up to 10' }
|
|
20
|
+
# ]
|
|
21
|
+
#
|
|
22
|
+
# FormatValidator.validate(user, nil, 'email', options)
|
|
23
|
+
#
|
|
24
|
+
# @param model [Volt::Model] the model being validated
|
|
25
|
+
# @param old_model [NilClass] no longer used, will be removed
|
|
26
|
+
# @param field_name [String] the name of the field being validated
|
|
27
|
+
#
|
|
28
|
+
# @param options (see #apply)
|
|
29
|
+
# @option options (see #apply)
|
|
30
|
+
#
|
|
31
|
+
# @return (see #errors)
|
|
32
|
+
def self.validate(model, old_model, field_name, options)
|
|
33
|
+
new(model, field_name).apply(options).errors
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param model [Volt::Model] the model being validated
|
|
37
|
+
# @param field_name [String] the name of the field being validated
|
|
38
|
+
def initialize(model, field_name)
|
|
39
|
+
@name = field_name
|
|
40
|
+
@value = model.read_attribute field_name
|
|
41
|
+
@criteria = []
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Applies criteria to the validator in a variety of forms
|
|
45
|
+
#
|
|
46
|
+
# @see .validate param examples
|
|
47
|
+
# @param options [Hash, Array<Hash>] criteria and related error messages
|
|
48
|
+
#
|
|
49
|
+
# @option options [Regexp, Proc] :with criterion for validation
|
|
50
|
+
# @option options [String] :message to display if criterion not met
|
|
51
|
+
# - will be appended to the field name
|
|
52
|
+
# - should start with something like:
|
|
53
|
+
# - +"must include..."+
|
|
54
|
+
# - +"should end with..."+
|
|
55
|
+
# - +"is invalid because..."+
|
|
56
|
+
#
|
|
57
|
+
# @return [self] returns itself for chaining
|
|
58
|
+
def apply(options)
|
|
59
|
+
return apply_list options if options.is_a? Array
|
|
60
|
+
with options[:with], options[:message]
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the first of the validation errors or an empty hash
|
|
65
|
+
#
|
|
66
|
+
# @return [Hash] hash of validation errors for the field
|
|
67
|
+
# - +{}+ if there are no errors
|
|
68
|
+
# - +{ field_name: [messages] }+ if there are errors
|
|
69
|
+
def errors
|
|
70
|
+
valid? ? {} : { @name => error_messages }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns an array of validation error messages
|
|
74
|
+
# @return [Array<String>]
|
|
75
|
+
def error_messages
|
|
76
|
+
@criteria.reduce([]) do |e, c|
|
|
77
|
+
test(c[:criterion]) ? e : e << c[:message]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns true or false depending on if the model passes all its criteria
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def valid?
|
|
84
|
+
error_messages.empty?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Adds a criterion and error message
|
|
88
|
+
#
|
|
89
|
+
# @param criterion [Regexp, Proc] criterion for validation
|
|
90
|
+
# @param message [String] to display if criterion not met
|
|
91
|
+
# - will be appended to the field name
|
|
92
|
+
# - should start with something like:
|
|
93
|
+
# - +"must include..."+
|
|
94
|
+
# - +"should end with..."+
|
|
95
|
+
# - +"is invalid because..."+
|
|
96
|
+
#
|
|
97
|
+
# @return (see #apply)
|
|
98
|
+
def with(criterion, message)
|
|
99
|
+
@criteria << { criterion: criterion, message: message }
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def apply_list(array)
|
|
106
|
+
array.each { |options| apply options }
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test(criterion)
|
|
111
|
+
return false unless @value.respond_to? :match
|
|
112
|
+
|
|
113
|
+
!!(criterion.try(:call, @value) || criterion.try(:match, @value))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -2,7 +2,7 @@ module Volt
|
|
|
2
2
|
class NumericalityValidator
|
|
3
3
|
def self.validate(model, old_model, field_name, args)
|
|
4
4
|
# Construct the class and return the errors
|
|
5
|
-
|
|
5
|
+
new(model, field_name, args).errors
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
attr_reader :errors
|
|
@@ -34,7 +34,7 @@ module Volt
|
|
|
34
34
|
case arg
|
|
35
35
|
when :min
|
|
36
36
|
if @value < val
|
|
37
|
-
|
|
37
|
+
add_error("number must be greater than #{val}")
|
|
38
38
|
end
|
|
39
39
|
when :max
|
|
40
40
|
if @value > val
|
|
@@ -1,40 +1,19 @@
|
|
|
1
1
|
module Volt
|
|
2
2
|
class PhoneNumberValidator
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
DEFAULT_OPTIONS = {
|
|
4
|
+
with: /^(\+?\d{1,2}[\.\-\ ]?\d{3}|\(\d{3}\)|\d{3})[\.\-\ ]?\d{3,4}[\.\-\ ]?\d{4}$/,
|
|
5
|
+
message: 'must be a phone number with area or country code'
|
|
6
|
+
}
|
|
5
7
|
|
|
6
8
|
def self.validate(model, old_model, field_name, options)
|
|
7
9
|
new(model, field_name, options).errors
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
+
def self.new(model, field_name, options)
|
|
13
|
+
options = DEFAULT_OPTIONS if options == true
|
|
14
|
+
options = DEFAULT_OPTIONS.merge options
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
when Hash, true, false
|
|
15
|
-
configure options
|
|
16
|
-
else
|
|
17
|
-
fail 'arguments can only be a Boolean or a Hash'
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def valid?
|
|
22
|
-
return false unless @value.is_a? String
|
|
23
|
-
|
|
24
|
-
!!@value.match(@custom_regex || DEFAULT_REGEX)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def errors
|
|
28
|
-
valid? ? {} : { phone_number: [ @custom_message || ERROR_MESSAGE ] }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def configure(options)
|
|
34
|
-
return unless options.is_a? Hash
|
|
35
|
-
|
|
36
|
-
@custom_message = options.fetch(:error_message) { nil }
|
|
37
|
-
@custom_regex = options.fetch(:with) { nil }
|
|
16
|
+
FormatValidator.new(model, field_name).apply options
|
|
38
17
|
end
|
|
39
18
|
end
|
|
40
19
|
end
|
|
@@ -10,12 +10,12 @@ module Volt
|
|
|
10
10
|
query = {}
|
|
11
11
|
# Check to see if any other documents have this value.
|
|
12
12
|
query[field_name.to_s] = value
|
|
13
|
-
query['_id'] = {'$ne' => model._id}
|
|
13
|
+
query['_id'] = { '$ne' => model._id }
|
|
14
14
|
|
|
15
15
|
# Check if the value is taken
|
|
16
16
|
# TODO: need a way to handle scope for unique
|
|
17
17
|
if $page.store.send(:"_#{model.path[-2]}").find(query).size > 0
|
|
18
|
-
message = (args.is_a?(Hash) && args[:message]) ||
|
|
18
|
+
message = (args.is_a?(Hash) && args[:message]) || 'is already taken'
|
|
19
19
|
|
|
20
20
|
errors[field_name] = [message]
|
|
21
21
|
end
|
|
@@ -80,7 +80,7 @@ module Volt
|
|
|
80
80
|
|
|
81
81
|
# TODORW: :parent => @value may change
|
|
82
82
|
item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
|
|
83
|
-
item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]]}
|
|
83
|
+
item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]] }
|
|
84
84
|
|
|
85
85
|
position_dependency = Dependency.new
|
|
86
86
|
item_context.locals[:_index_dependency] = position_dependency
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Volt
|
|
2
|
+
# ViewFinderForPath helps find the location of view file given
|
|
3
|
+
# a template path.
|
|
4
|
+
class ViewLookupForPath
|
|
5
|
+
# Takes in the path of the current view file. This allows for relative paths
|
|
6
|
+
# to be run.
|
|
7
|
+
#
|
|
8
|
+
# @param [Page] the page object
|
|
9
|
+
# @param [String] the path of the current view
|
|
10
|
+
def initialize(page, binding_in_path)
|
|
11
|
+
@page = page
|
|
12
|
+
path_parts = binding_in_path.split('/')
|
|
13
|
+
@collection_name = path_parts[0]
|
|
14
|
+
@controller_name = path_parts[1]
|
|
15
|
+
@page_name = path_parts[2]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns true if there is a template at the path
|
|
19
|
+
def check_for_template?(path)
|
|
20
|
+
@page.templates[path]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Takes in a lookup path and returns the full path for the matching
|
|
24
|
+
# template. Also returns the controller and action name if applicable.
|
|
25
|
+
#
|
|
26
|
+
# Looking up a path is fairly simple. There are 4 parts needed to find
|
|
27
|
+
# the html to be rendered. File paths look like this:
|
|
28
|
+
# app/{component}/views/{controller_name}/{view}.html
|
|
29
|
+
# Within the html file may be one or more sections.
|
|
30
|
+
# 1. component (app/{comp})
|
|
31
|
+
# 2. controller
|
|
32
|
+
# 3. view
|
|
33
|
+
# 4. sections
|
|
34
|
+
#
|
|
35
|
+
# When searching for a file, the lookup starts at the section, and moves up.
|
|
36
|
+
# when moving up, default values are provided for the section, then view/section, etc..
|
|
37
|
+
# until a file is either found or the component level is reached.
|
|
38
|
+
#
|
|
39
|
+
# The defaults are as follows:
|
|
40
|
+
# 1. component - main
|
|
41
|
+
# 2. controller - main
|
|
42
|
+
# 3. view - index
|
|
43
|
+
# 4. section - body
|
|
44
|
+
def path_for_template(lookup_path, force_section = nil)
|
|
45
|
+
parts = lookup_path.split('/')
|
|
46
|
+
parts_size = parts.size
|
|
47
|
+
|
|
48
|
+
default_parts = %w(main main index body)
|
|
49
|
+
|
|
50
|
+
# When forcing a sub template, we can default the sub template section
|
|
51
|
+
default_parts[-1] = force_section if force_section
|
|
52
|
+
|
|
53
|
+
(5 - parts_size).times do |path_position|
|
|
54
|
+
# If they passed in a force_section, we can skip the first
|
|
55
|
+
next if force_section && path_position == 0
|
|
56
|
+
|
|
57
|
+
full_path = [@collection_name, @controller_name, @page_name, nil]
|
|
58
|
+
|
|
59
|
+
start_at = full_path.size - parts_size - path_position
|
|
60
|
+
|
|
61
|
+
full_path.size.times do |index|
|
|
62
|
+
if index >= start_at
|
|
63
|
+
if (part = parts[index - start_at])
|
|
64
|
+
full_path[index] = part
|
|
65
|
+
else
|
|
66
|
+
full_path[index] = default_parts[index]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
path = full_path.join('/')
|
|
72
|
+
if check_for_template?(path)
|
|
73
|
+
controller = nil
|
|
74
|
+
|
|
75
|
+
if path_position >= 1
|
|
76
|
+
init_method = full_path[2]
|
|
77
|
+
else
|
|
78
|
+
init_method = full_path[3]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Lookup the controller
|
|
82
|
+
controller = [full_path[0], full_path[1] + '_controller', init_method]
|
|
83
|
+
|
|
84
|
+
return path, controller
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
[nil, nil]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|