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.
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