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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/{Readme.md → README.md} +9 -12
  6. data/Rakefile +2 -9
  7. data/VERSION +1 -1
  8. data/app/volt/models/user.rb +8 -0
  9. data/app/volt/tasks/live_query/data_store.rb +13 -5
  10. data/app/volt/tasks/live_query/live_query.rb +45 -3
  11. data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
  12. data/app/volt/tasks/query_tasks.rb +20 -2
  13. data/app/volt/tasks/store_tasks.rb +37 -20
  14. data/app/volt/tasks/user_tasks.rb +15 -13
  15. data/lib/volt/boot.rb +5 -3
  16. data/lib/volt/cli/console.rb +1 -0
  17. data/lib/volt/cli/generate.rb +15 -0
  18. data/lib/volt/cli.rb +19 -12
  19. data/lib/volt/config.rb +1 -1
  20. data/lib/volt/controllers/model_controller.rb +13 -3
  21. data/lib/volt/extra_core/extra_core.rb +1 -0
  22. data/lib/volt/extra_core/hash.rb +26 -0
  23. data/lib/volt/extra_core/object.rb +5 -1
  24. data/lib/volt/models/array_model.rb +86 -35
  25. data/lib/volt/models/associations.rb +53 -0
  26. data/lib/volt/models/buffer.rb +22 -10
  27. data/lib/volt/models/dirty.rb +88 -0
  28. data/lib/volt/models/errors.rb +21 -0
  29. data/lib/volt/models/field_helpers.rb +2 -2
  30. data/lib/volt/models/listener_tracker.rb +17 -0
  31. data/lib/volt/models/model.rb +213 -69
  32. data/lib/volt/models/model_helpers.rb +27 -17
  33. data/lib/volt/models/permissions.rb +246 -0
  34. data/lib/volt/models/persistors/array_store.rb +149 -81
  35. data/lib/volt/models/persistors/base.rb +16 -0
  36. data/lib/volt/models/persistors/cookies.rb +14 -9
  37. data/lib/volt/models/persistors/flash.rb +3 -0
  38. data/lib/volt/models/persistors/local_store.rb +0 -16
  39. data/lib/volt/models/persistors/model_store.rb +1 -2
  40. data/lib/volt/models/persistors/query/normalizer.rb +51 -0
  41. data/lib/volt/models/persistors/query/query_listener.rb +21 -5
  42. data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
  43. data/lib/volt/models/persistors/store.rb +8 -0
  44. data/lib/volt/models/persistors/store_state.rb +4 -27
  45. data/lib/volt/models/state_helpers.rb +11 -0
  46. data/lib/volt/models/state_manager.rb +43 -0
  47. data/lib/volt/models/url.rb +5 -5
  48. data/lib/volt/models/validations.rb +38 -41
  49. data/lib/volt/models/validators/email_validator.rb +4 -9
  50. data/lib/volt/models/validators/format_validator.rb +23 -8
  51. data/lib/volt/models/validators/length_validator.rb +2 -2
  52. data/lib/volt/models/validators/numericality_validator.rb +7 -3
  53. data/lib/volt/models/validators/phone_number_validator.rb +4 -9
  54. data/lib/volt/models/validators/presence_validator.rb +2 -2
  55. data/lib/volt/models/validators/unique_validator.rb +2 -2
  56. data/lib/volt/models/validators/user_validation.rb +6 -0
  57. data/lib/volt/models.rb +8 -3
  58. data/lib/volt/page/bindings/attribute_binding.rb +10 -4
  59. data/lib/volt/page/bindings/content_binding.rb +9 -5
  60. data/lib/volt/page/bindings/if_binding.rb +25 -2
  61. data/lib/volt/page/bindings/template_binding.rb +19 -1
  62. data/lib/volt/page/bindings/yield_binding.rb +31 -0
  63. data/lib/volt/page/page.rb +11 -16
  64. data/lib/volt/reactive/class_eventable.rb +71 -0
  65. data/lib/volt/reactive/computation.rb +79 -10
  66. data/lib/volt/reactive/dependency.rb +27 -8
  67. data/lib/volt/reactive/eventable.rb +36 -22
  68. data/lib/volt/reactive/reactive_array.rb +2 -3
  69. data/lib/volt/reactive/reactive_hash.rb +8 -3
  70. data/lib/volt/router/routes.rb +2 -1
  71. data/lib/volt/server/component_templates.rb +0 -2
  72. data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
  73. data/lib/volt/server/html_parser/view_handler.rb +3 -0
  74. data/lib/volt/server/html_parser/view_parser.rb +1 -0
  75. data/lib/volt/server/html_parser/view_scope.rb +17 -41
  76. data/lib/volt/server/rack/component_paths.rb +1 -10
  77. data/lib/volt/server/rack/index_files.rb +9 -4
  78. data/lib/volt/server/rack/opal_files.rb +22 -14
  79. data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
  80. data/lib/volt/server/socket_connection_handler.rb +4 -0
  81. data/lib/volt/spec/setup.rb +26 -0
  82. data/lib/volt/tasks/dispatcher.rb +11 -0
  83. data/lib/volt/utils/event_counter.rb +29 -0
  84. data/lib/volt/utils/generic_pool.rb +12 -0
  85. data/lib/volt/utils/modes.rb +40 -0
  86. data/lib/volt/utils/promise_patch.rb +66 -0
  87. data/lib/volt/utils/timers.rb +33 -0
  88. data/lib/volt/volt/users.rb +48 -5
  89. data/lib/volt.rb +4 -0
  90. data/spec/apps/kitchen_sink/Gemfile +3 -1
  91. data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
  92. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
  93. data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
  94. data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
  95. data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
  96. data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
  97. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
  98. data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
  99. data/spec/extra_core/logger_spec.rb +4 -2
  100. data/spec/integration/user_spec.rb +42 -42
  101. data/spec/integration/yield_spec.rb +18 -0
  102. data/spec/models/associations_spec.rb +37 -0
  103. data/spec/models/dirty_spec.rb +102 -0
  104. data/spec/models/model_spec.rb +64 -8
  105. data/spec/models/model_state_spec.rb +24 -0
  106. data/spec/models/permissions_spec.rb +96 -0
  107. data/spec/models/user_spec.rb +8 -5
  108. data/spec/models/user_validation_spec.rb +24 -0
  109. data/spec/models/validations_spec.rb +44 -5
  110. data/spec/models/validators/email_validator_spec.rb +109 -82
  111. data/spec/models/validators/format_validator_spec.rb +4 -107
  112. data/spec/models/validators/length_validator_spec.rb +9 -9
  113. data/spec/models/validators/phone_number_validator_spec.rb +60 -103
  114. data/spec/models/validators/shared_examples_for_validators.rb +123 -0
  115. data/spec/reactive/class_eventable_spec.rb +37 -0
  116. data/spec/reactive/computation_spec.rb +68 -3
  117. data/spec/reactive/dependency_spec.rb +71 -0
  118. data/spec/reactive/eventable_spec.rb +21 -0
  119. data/spec/reactive/reactive_hash_spec.rb +12 -0
  120. data/spec/router/routes_spec.rb +50 -50
  121. data/spec/server/html_parser/view_parser_spec.rb +0 -3
  122. data/spec/server/rack/component_paths_spec.rb +11 -0
  123. data/spec/server/rack/quite_common_logger_spec.rb +3 -4
  124. data/spec/spec_helper.rb +7 -3
  125. data/templates/component/config/dependencies.rb +1 -7
  126. data/templates/component/config/routes.rb +1 -1
  127. data/templates/component/controllers/main_controller.rb.tt +20 -0
  128. data/templates/component/views/{index → main}/index.html.tt +0 -0
  129. data/templates/newgem/lib/newgem.rb.tt +1 -3
  130. data/templates/project/app/main/config/routes.rb +3 -3
  131. data/templates/project/app/main/views/main/main.html.tt +4 -4
  132. data/templates/project/config/app.rb.tt +6 -0
  133. data/volt.gemspec +11 -7
  134. metadata +96 -42
  135. data/lib/volt/models/model_state.rb +0 -21
  136. 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.nosave do
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
- puts "Error adding listener: #{err.inspect}"
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
- @stores.first.model.each_with_index do |item, index|
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(:loaded)
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
@@ -34,6 +34,14 @@ module Volt
34
34
  end
35
35
  model
36
36
  end
37
+
38
+ def clear_identity_map
39
+ @@identity_map.clear
40
+ end
41
+
42
+ def inspect
43
+ "<#{self.class.to_s}:#{object_id}>"
44
+ end
37
45
  end
38
46
  end
39
47
  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
- change_state_to(initial_state || :not_loaded)
8
- end
9
-
10
- def state
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,11 @@
1
+ module Volt
2
+ module StateHelpers
3
+ def loaded_state
4
+ state_for(:loaded_state)
5
+ end
6
+
7
+ def loaded?
8
+ loaded_state == :loaded
9
+ end
10
+ end
11
+ 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
@@ -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[1..-1]}=#{value}"
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.send(:"#{name}=", new_val)
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.send(name), value)
192
+ assign_new(params.get(name), value)
193
193
  else
194
194
  # assign
195
- params.send(:"#{name}=", value)
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?).map { |v| :"_#{v}" }
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
- # require 'volt/models/validations/errors'
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
- # TODO: Errors is being called for any validation change. We should have errors return a
70
- # hash like object that only calls the validation for each one.
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
- # Get the previous model from the buffer
82
- save_to = options[:save_to]
83
- if save_to && save_to.is_a?(Volt::Model)
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
- old_model = nil
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
- errors = run_validations(errors, merge, marked_only, old_model)
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 = merge.call(server_errors.to_h)
89
+ errors.merge!(server_errors.to_h)
94
90
  end
95
91
 
96
- errors = run_custom_validations(errors, merge, old_model)
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(errors, merge, marked_only, old_model)
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
- validate_with(merge, klass, old_model, field_name, args)
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(errors, merge, old_model)
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, passes in
136
- # the old_model as an argument
137
- result = instance_exec(old_model, &custom_validation)
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
- def self.validate(model, old_model, field_name, options)
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
- FormatValidator.new(model, field_name).apply options
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, nil, 'email', options)
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, nil, 'email', options)
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, old_model, field_name, options)
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.read_attribute field_name
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, old_model, field_name, args)
3
+ def self.validate(model, field_name, args)
4
4
  errors = {}
5
- value = model.read_attribute(field_name)
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, old_model, field_name, args)
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.read_attribute(field_name)
15
+ @value = model.get(field_name)
16
16
 
17
17
  # Convert to float if it is a string for a float
18
- @value = Kernel.Float(@value) rescue nil
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
- def self.validate(model, old_model, field_name, options)
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
- FormatValidator.new(model, field_name).apply options
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, old_model, field_name, args)
3
+ def self.validate(model, field_name, args)
4
4
  errors = {}
5
- value = model.read_attribute(field_name)
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, old_model, field_name, args)
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.read_attribute(field_name)
8
+ value = model.get(field_name)
9
9
 
10
10
  query = {}
11
11
  # Check to see if any other documents have this value.
@@ -0,0 +1,6 @@
1
+ module Volt
2
+ # Provide methods on the model class to more easily setup user validations
3
+ module UserValidatorHelpers
4
+
5
+ end
6
+ end
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.rb'
14
+ require 'promise'
15
15
  else
16
16
  # Opal doesn't expose its promise library directly
17
- spec = Gem::Specification.find_by_name('opal')
18
- require(spec.gem_dir + '/stdlib/promise')
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'