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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.md +7 -2
  5. data/CONTRIBUTING.md +133 -0
  6. data/Gemfile +0 -1
  7. data/Rakefile +11 -3
  8. data/Readme.md +4 -4
  9. data/VERSION +1 -1
  10. data/app/volt/models/user.rb +6 -3
  11. data/lib/volt/cli/console.rb +21 -12
  12. data/lib/volt/cli/runner.rb +1 -1
  13. data/lib/volt/cli.rb +5 -4
  14. data/lib/volt/config.rb +9 -11
  15. data/lib/volt/controllers/model_controller.rb +16 -0
  16. data/lib/volt/data_stores/data_store.rb +1 -1
  17. data/lib/volt/extra_core/array.rb +1 -6
  18. data/lib/volt/extra_core/blank.rb +1 -3
  19. data/lib/volt/extra_core/class.rb +1 -1
  20. data/lib/volt/extra_core/extra_core.rb +0 -1
  21. data/lib/volt/extra_core/logger.rb +78 -1
  22. data/lib/volt/extra_core/object.rb +4 -4
  23. data/lib/volt/models/array_model.rb +2 -3
  24. data/lib/volt/models/buffer.rb +1 -2
  25. data/lib/volt/models/field_helpers.rb +4 -5
  26. data/lib/volt/models/model.rb +27 -1
  27. data/lib/volt/models/model_hash_behaviour.rb +3 -4
  28. data/lib/volt/models/persistors/array_store.rb +6 -7
  29. data/lib/volt/models/persistors/cookies.rb +2 -2
  30. data/lib/volt/models/persistors/model_store.rb +5 -6
  31. data/lib/volt/models/validations.rb +5 -7
  32. data/lib/volt/models/validators/email_validator.rb +8 -29
  33. data/lib/volt/models/validators/format_validator.rb +116 -0
  34. data/lib/volt/models/validators/numericality_validator.rb +2 -2
  35. data/lib/volt/models/validators/phone_number_validator.rb +8 -29
  36. data/lib/volt/models/validators/unique_validator.rb +2 -2
  37. data/lib/volt/page/bindings/content_binding.rb +1 -1
  38. data/lib/volt/page/bindings/each_binding.rb +1 -1
  39. data/lib/volt/page/bindings/template_binding/view_lookup_for_path.rb +92 -0
  40. data/lib/volt/page/bindings/template_binding.rb +10 -85
  41. data/lib/volt/page/channel.rb +0 -1
  42. data/lib/volt/page/page.rb +5 -7
  43. data/lib/volt/page/sub_context.rb +1 -1
  44. data/lib/volt/page/targets/base_section.rb +2 -2
  45. data/lib/volt/page/targets/helpers/comment_searchers.rb +2 -2
  46. data/lib/volt/reactive/reactive_accessors.rb +1 -1
  47. data/lib/volt/reactive/reactive_array.rb +6 -6
  48. data/lib/volt/router/routes.rb +4 -4
  49. data/lib/volt/server/rack/asset_files.rb +1 -2
  50. data/lib/volt/server/rack/component_code.rb +0 -2
  51. data/lib/volt/server/rack/component_paths.rb +2 -2
  52. data/lib/volt/server/rack/quiet_common_logger.rb +2 -2
  53. data/lib/volt/spec/capybara.rb +1 -1
  54. data/lib/volt/spec/sauce_labs.rb +6 -6
  55. data/lib/volt/spec/setup.rb +8 -7
  56. data/lib/volt/tasks/dispatcher.rb +12 -10
  57. data/lib/volt/tasks/task_handler.rb +1 -1
  58. data/lib/volt/utils/generic_pool.rb +2 -2
  59. data/lib/volt/volt/users.rb +7 -9
  60. data/lib/volt.rb +2 -4
  61. data/spec/apps/file_loading/app/missing_deps/config/dependencies.rb +1 -1
  62. data/spec/apps/kitchen_sink/Gemfile +2 -2
  63. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +1 -1
  64. data/spec/apps/kitchen_sink/app/main/config/routes.rb +0 -1
  65. data/spec/apps/kitchen_sink/app/main/models/user.rb +1 -1
  66. data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -1
  67. data/spec/apps/kitchen_sink/config/app.rb +1 -1
  68. data/spec/extra_core/array_spec.rb +4 -2
  69. data/spec/extra_core/blank_spec.rb +11 -0
  70. data/spec/extra_core/class_spec.rb +2 -2
  71. data/spec/extra_core/logger_spec.rb +50 -0
  72. data/spec/extra_core/string_transformations_spec.rb +0 -1
  73. data/spec/integration/cookies_spec.rb +1 -2
  74. data/spec/integration/flash_spec.rb +2 -3
  75. data/spec/integration/list_spec.rb +1 -1
  76. data/spec/integration/templates_spec.rb +0 -1
  77. data/spec/integration/url_spec.rb +1 -2
  78. data/spec/integration/user_spec.rb +3 -3
  79. data/spec/models/field_helpers_spec.rb +2 -2
  80. data/spec/models/model_spec.rb +21 -2
  81. data/spec/models/user_spec.rb +69 -0
  82. data/spec/models/validations_spec.rb +69 -78
  83. data/spec/models/validators/email_validator_spec.rb +3 -3
  84. data/spec/models/validators/format_validator_spec.rb +144 -0
  85. data/spec/models/validators/length_validator_spec.rb +82 -0
  86. data/spec/models/validators/phone_number_validator_spec.rb +3 -3
  87. data/spec/page/bindings/template_binding/view_lookup_for_path_spec.rb +149 -0
  88. data/spec/page/bindings/template_binding_spec.rb +0 -151
  89. data/spec/reactive/computation_spec.rb +46 -0
  90. data/spec/reactive/dependency_spec.rb +0 -1
  91. data/spec/reactive/reactive_array_spec.rb +0 -1
  92. data/spec/router/routes_spec.rb +0 -4
  93. data/spec/server/html_parser/view_parser_spec.rb +0 -4
  94. data/spec/server/rack/asset_files_spec.rb +1 -1
  95. data/spec/server/rack/quite_common_logger_spec.rb +55 -0
  96. data/spec/spec_helper.rb +2 -5
  97. data/spec/tasks/dispatcher_spec.rb +16 -5
  98. data/spec/tasks/live_query_spec.rb +0 -1
  99. data/spec/tasks/query_tasks.rb +0 -1
  100. data/spec/tasks/query_tracker_spec.rb +0 -3
  101. data/spec/templates/targets/binding_document/component_node_spec.rb +0 -1
  102. data/spec/utils/generic_counting_pool_spec.rb +0 -1
  103. data/spec/utils/generic_pool_spec.rb +0 -1
  104. data/templates/component/assets/images/.empty_directory +0 -0
  105. data/templates/project/README.md.tt +3 -2
  106. data/templates/project/app/main/assets/images/.empty_directory +0 -0
  107. data/templates/project/config/base/index.html +6 -7
  108. data/volt.gemspec +3 -5
  109. metadata +27 -9
  110. data/lib/volt/extra_core/numeric.rb +0 -9
@@ -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 = attrs.delete(:_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 "Unload Data"
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
- return Cursor.new([], @model.options.merge(query: query))
128
+ Cursor.new([], @model.options.merge(query: query))
130
129
  end
131
130
 
132
131
  def limit(limit)
133
- return Cursor.new([], @model.options.merge(limit: limit))
132
+ Cursor.new([], @model.options.merge(limit: limit))
134
133
  end
135
134
 
136
135
  def skip(skip)
137
- return Cursor.new([], @model.options.merge(skip: skip))
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
- raise "then must pass a block" unless block
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 ; `decodeURIComponent(p)` }
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
- return !(defined?($loading_models) && $loading_models) && @tasks
65
+ !(defined?($loading_models) && $loading_models) && @tasks
66
66
  else
67
- return true
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
- return {}
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
- raise "validate should be passed a field name and options or a block, not both."
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
- return errors
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
- return errors
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
- DEFAULT_REGEX = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
4
- ERROR_MESSAGE = 'must be an email address'
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 initialize(model, field_name, options)
11
- @value = model.read_attribute field_name
12
+ def self.new(model, field_name, options)
13
+ options = DEFAULT_OPTIONS if options == true
14
+ options = DEFAULT_OPTIONS.merge options
12
15
 
13
- case options
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
- self.new(model, field_name, args).errors
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
- add_error("number must be greater than #{val}")
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
- DEFAULT_REGEX = /^(\+?\d{1,2}[\.\-\ ]?\d{3}|\(\d{3}\)|\d{3})[\.\-\ ]?\d{3,4}[\.\-\ ]?\d{4}$/
4
- ERROR_MESSAGE = 'must be a phone number with area or country code'
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 initialize(model, field_name, options)
11
- @value = model.read_attribute field_name
12
+ def self.new(model, field_name, options)
13
+ options = DEFAULT_OPTIONS if options == true
14
+ options = DEFAULT_OPTIONS.merge options
12
15
 
13
- case options
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]) || "is already taken"
18
+ message = (args.is_a?(Hash) && args[:message]) || 'is already taken'
19
19
 
20
20
  errors[field_name] = [message]
21
21
  end
@@ -25,7 +25,7 @@ module Volt
25
25
 
26
26
  # Update the html in this section
27
27
  # TODO: Move the formatter into another class.
28
- dom_section.html = value.gsub("\n", "<br />\n")
28
+ dom_section.text = value.gsub("\n", "<br />\n")
29
29
  end
30
30
 
31
31
  def remove
@@ -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