volt 0.8.27.beta2 → 0.8.27.beta3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|