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
@@ -3,10 +3,20 @@ require 'volt/models/array_model'
3
3
  require 'volt/models/model_helpers'
4
4
  require 'volt/models/model_hash_behaviour'
5
5
  require 'volt/models/validations'
6
- require 'volt/models/model_state'
6
+ require 'volt/utils/modes'
7
+ require 'volt/models/state_manager'
8
+ require 'volt/models/state_helpers'
7
9
  require 'volt/models/buffer'
8
10
  require 'volt/models/field_helpers'
9
11
  require 'volt/reactive/reactive_hash'
12
+ require 'volt/models/validators/user_validation'
13
+ require 'volt/models/dirty'
14
+ require 'volt/models/listener_tracker'
15
+ require 'volt/models/permissions'
16
+ require 'volt/models/associations'
17
+ require 'volt/reactive/class_eventable'
18
+ require 'volt/utils/event_counter'
19
+ require 'thread'
10
20
 
11
21
  module Volt
12
22
  class NilMethodCall < NoMethodError
@@ -21,13 +31,20 @@ module Volt
21
31
  include ModelWrapper
22
32
  include ModelHelpers
23
33
  include ModelHashBehaviour
34
+ include StateManager
35
+ include StateHelpers
24
36
  include Validations
25
- include ModelState
26
37
  include Buffer
27
38
  include FieldHelpers
39
+ include UserValidatorHelpers
40
+ include Dirty
41
+ include ClassEventable
42
+ include Modes
43
+ include ListenerTracker
44
+ include Permissions
45
+ include Associations
28
46
 
29
- attr_reader :attributes
30
- attr_reader :parent, :path, :persistor, :options
47
+ attr_reader :attributes, :parent, :path, :persistor, :options
31
48
 
32
49
  INVALID_FIELD_NAMES = {
33
50
  :attributes => true,
@@ -38,17 +55,49 @@ module Volt
38
55
  }
39
56
 
40
57
  def initialize(attributes = {}, options = {}, initial_state = nil)
58
+ # The listener event counter keeps track of how many computations are listening on this model
59
+ @listener_event_counter = EventCounter.new(
60
+ -> { parent.try(:persistor).try(:listener_added) },
61
+ -> { parent.try(:persistor).try(:listener_removed) }
62
+ )
63
+
64
+ # The root dependency is used to track if anything is using the data from this
65
+ # model. That information is relayed to the ArrayModel so it knows when it can
66
+ # stop subscribing.
67
+ # @root_dep = Dependency.new(@listener_event_counter.method(:add), @listener_event_counter.method(:remove))
68
+ @root_dep = Dependency.new(-> { add_list }, -> { remove_list })
69
+
41
70
  @deps = HashDependency.new
42
71
  @size_dep = Dependency.new
43
72
  self.options = options
44
73
 
45
- send(:attributes=, attributes, true)
74
+ @new = (initial_state != :loaded)
75
+
76
+ assign_attributes(attributes, true)
46
77
 
47
- # Models start in a loaded state since they are normally setup from an
48
- # ArrayModel, which will have the data when they get added.
49
- @state = :loaded
78
+ # The persistor is usually responsible for setting up the loaded_state, if
79
+ # there is no persistor, we set it to loaded
80
+ if @persistor
81
+ @persistor.loaded(initial_state)
82
+ else
83
+ change_state_to(:loaded_state, :loaded, false)
84
+ end
50
85
 
51
- @persistor.loaded(initial_state) if @persistor
86
+ # Trigger the new event, pass in :new
87
+ trigger!(:new, :new)
88
+ end
89
+
90
+ def add_list
91
+ @listener_event_counter.add
92
+ end
93
+
94
+ def remove_list
95
+ @listener_event_counter.remove
96
+ end
97
+
98
+ def state_for(*args)
99
+ @root_dep.depend
100
+ super
52
101
  end
53
102
 
54
103
  # the id is stored in a field named _id, so we setup _id to proxy to this
@@ -60,6 +109,11 @@ module Volt
60
109
  self.__id = val
61
110
  end
62
111
 
112
+ # Return true if the model hasn't been saved yet
113
+ def new?
114
+ @new
115
+ end
116
+
63
117
  # Update the options
64
118
  def options=(options)
65
119
  @options = options
@@ -70,31 +124,22 @@ module Volt
70
124
  end
71
125
 
72
126
  # Assign multiple attributes as a hash, directly.
73
- def attributes=(attrs, initial_setup = false)
127
+ def assign_attributes(attrs, initial_setup = false)
74
128
  @attributes = {}
75
129
 
76
130
  attrs = wrap_values(attrs)
77
131
 
78
132
  if attrs
79
- # Assign id first
80
- id = attrs.delete(:_id)
81
-
82
- # When doing a mass-assign, we don't save until the end.
83
- Model.nosave do
84
- self._id = id if id
85
-
86
- # Assign each attribute using setters
87
- attrs.each_pair do |key, value|
88
- if self.respond_to?(:"#{key}=")
89
- # If a method without an underscore is defined, call that.
90
- send(:"#{key}=", value)
91
- else
92
- # Otherwise, use the _ version
93
- send(:"_#{key}=", value)
94
- end
133
+ # When doing a mass-assign, we don't validate or save until the end.
134
+ if initial_setup
135
+ Model.no_change_tracking do
136
+ assign_all_attributes(attrs)
95
137
  end
138
+ else
139
+ assign_all_attributes(attrs)
96
140
  end
97
141
  else
142
+ # Assign to nil
98
143
  @attributes = attrs
99
144
  end
100
145
 
@@ -102,18 +147,22 @@ module Volt
102
147
  @deps.changed_all!
103
148
  @deps = HashDependency.new
104
149
 
105
- unless initial_setup
150
+ # Save the changes
151
+ if initial_setup
152
+ # Run initial validation
153
+ errs = Volt.in_mode?(:no_validate) ? nil : validate!
106
154
 
107
- # Let the persistor know something changed
108
- if @persistor
109
- # the changed method on a persistor should return a promise that will
110
- # be resolved when the save is complete, or fail with a hash of errors.
111
- return @persistor.changed
155
+ if errs && errs.size > 0
156
+ return Promise.new.reject(errs)
157
+ else
158
+ return Promise.new.resolve(nil)
112
159
  end
160
+ else
161
+ return run_changed
113
162
  end
114
163
  end
115
164
 
116
- alias_method :assign_attributes, :attributes=
165
+ alias_method :attributes=, :assign_attributes
117
166
 
118
167
  # Pass the comparison through
119
168
  def ==(val)
@@ -133,14 +182,13 @@ module Volt
133
182
 
134
183
  def method_missing(method_name, *args, &block)
135
184
  if method_name[0] == '_'
136
-
137
185
  # Remove underscore
138
186
  method_name = method_name[1..-1]
139
187
  if method_name[-1] == '='
140
188
  # Assigning an attribute without the =
141
- assign_attribute(method_name[0..-2], *args, &block)
189
+ set(method_name[0..-2], args[0], &block)
142
190
  else
143
- read_attribute(method_name)
191
+ get(method_name)
144
192
  end
145
193
  else
146
194
  # Call on parent
@@ -149,19 +197,21 @@ module Volt
149
197
  end
150
198
 
151
199
  # Do the assignment to a model and trigger a changed event
152
- def assign_attribute(method_name, *args, &block)
200
+ def set(attribute_name, value, &block)
153
201
  self.expand!
154
202
  # Assign, without the =
155
- attribute_name = method_name.to_sym
203
+ attribute_name = attribute_name.to_sym
156
204
 
157
205
  check_valid_field_name(attribute_name)
158
206
 
159
- value = args[0]
160
-
161
207
  old_value = @attributes[attribute_name]
162
208
  new_value = wrap_value(value, [attribute_name])
163
209
 
164
210
  if old_value != new_value
211
+ # Track the old value, skip if we are in no_validate
212
+ attribute_will_change!(attribute_name, old_value) unless Volt.in_mode?(:no_change_tracking)
213
+
214
+ # Assign the new value
165
215
  @attributes[attribute_name] = new_value
166
216
 
167
217
  @deps.changed!(attribute_name)
@@ -172,13 +222,10 @@ module Volt
172
222
 
173
223
  # TODO: Can we make this so it doesn't need to be handled for non store collections
174
224
  # (maybe move it to persistor, though thats weird since buffers don't have a persistor)
175
- clear_server_errors(attribute_name) if @server_errors
225
+ clear_server_errors(attribute_name) if @server_errors.present?
176
226
 
177
- # Don't save right now if we're in a nosave block
178
- if !defined?(Thread) || !Thread.current['nosave']
179
- # Let the persistor know something changed
180
- @persistor.changed(attribute_name) if @persistor
181
- end
227
+ # Save the changes
228
+ run_changed(attribute_name) unless Volt.in_mode?(:no_change_tracking)
182
229
  end
183
230
  end
184
231
 
@@ -186,14 +233,14 @@ module Volt
186
233
  # 1) a nil model, which returns a wrapped error
187
234
  # 2) reading directly from attributes
188
235
  # 3) trying to read a key that doesn't exist.
189
- def read_attribute(attr_name)
236
+ def get(attr_name)
190
237
  # Reading an attribute, we may get back a nil model.
191
238
  attr_name = attr_name.to_sym
192
239
 
193
240
  check_valid_field_name(attr_name)
194
241
 
195
- # Track dependency
196
- # @deps.depend(attr_name)
242
+ # Track that something is listening
243
+ @root_dep.depend
197
244
 
198
245
  # See if the value is in attributes
199
246
  if @attributes && @attributes.key?(attr_name)
@@ -223,6 +270,10 @@ module Volt
223
270
  end
224
271
  end
225
272
 
273
+ def respond_to_missing?(method_name, include_private=false)
274
+ method_name.to_s.start_with?('_') || super
275
+ end
276
+
226
277
  # Get a new model, make it easy to override
227
278
  def read_new_model(method_name)
228
279
  if @persistor && @persistor.respond_to?(:read_new_model)
@@ -238,13 +289,13 @@ module Volt
238
289
  end
239
290
 
240
291
  def new_model(attributes, options)
241
- class_at_path(options[:path]).new(attributes, options)
292
+ Volt::Model.class_at_path(options[:path]).new(attributes, options)
242
293
  end
243
294
 
244
295
  def new_array_model(attributes, options)
245
296
  # Start with an empty query
246
297
  options = options.dup
247
- options[:query] = {}
298
+ options[:query] = []
248
299
 
249
300
  ArrayModel.new(attributes, options)
250
301
  end
@@ -287,29 +338,43 @@ module Volt
287
338
  end
288
339
 
289
340
  def inspect
290
- "<#{self.class}:#{object_id} #{attributes.inspect}>"
341
+ Computation.run_without_tracking do
342
+ str = "<#{self.class}:#{object_id}"
343
+
344
+ # Get path, loaded_state, and persistor, but cache in local var
345
+ path = self.path
346
+ str += " path:#{path}" if path
347
+
348
+ loaded_state = self.loaded_state
349
+ str += " state:#{loaded_state}" if loaded_state
350
+
351
+ persistor = self.persistor
352
+ # str += " persistor:#{persistor.inspect}" if persistor
353
+ str += " #{attributes.inspect}>"
354
+
355
+ str
356
+ end
291
357
  end
292
358
 
293
- # Takes a block that when run, changes to models will not save inside of
294
- if RUBY_PLATFORM == 'opal'
295
- # Temporary stub for no save on client
296
- def self.nosave
297
- yield
359
+ def destroy
360
+ if parent
361
+ result = parent.delete(self)
362
+
363
+ # Wrap result in a promise if it isn't one
364
+ return Promise.new.then { result }
365
+ else
366
+ fail "Model does not have a parent and cannot be deleted."
298
367
  end
299
- else
300
- def self.nosave
301
- previous = Thread.current['nosave']
302
- Thread.current['nosave'] = true
303
- begin
304
- yield
305
- ensure
306
- Thread.current['nosave'] = previous
307
- end
368
+ end
369
+
370
+ # Setup run mode helpers
371
+ [:no_save, :no_validate, :no_change_tracking].each do |method_name|
372
+ define_singleton_method(method_name) do |&block|
373
+ Volt.run_in_mode(method_name, &block)
308
374
  end
309
375
  end
310
376
 
311
377
  private
312
-
313
378
  # Volt provides a few access methods to get more data about the model,
314
379
  # we want to prevent these from being assigned or accessed through
315
380
  # underscore methods.
@@ -320,8 +385,14 @@ module Volt
320
385
  end
321
386
 
322
387
  def setup_buffer(model)
323
- model.attributes = attributes
324
- model.change_state_to(:loaded)
388
+ Volt::Model.no_validate do
389
+ model.assign_attributes(attributes, true)
390
+ end
391
+
392
+ model.change_state_to(:loaded_state, :loaded)
393
+
394
+ # Set new to the same as the main model the buffer is from
395
+ model.instance_variable_set('@new', @new)
325
396
  end
326
397
 
327
398
  # Takes the persistor if there is one and
@@ -330,5 +401,78 @@ module Volt
330
401
  @persistor = persistor.new(self)
331
402
  end
332
403
  end
404
+
405
+ # Used internally from other methods that assign all attributes
406
+ def assign_all_attributes(attrs)
407
+ # Assign each attribute using setters
408
+ attrs.each_pair do |key, value|
409
+ key = key.to_sym
410
+
411
+ # Track the change, since assign_all_attributes runs with no_change_tracking
412
+ # attribute_will_change!(key, @attributes[key])
413
+
414
+ if self.respond_to?(:"#{key}=")
415
+ # If a method without an underscore is defined, call that.
416
+ send(:"#{key}=", value)
417
+ else
418
+ # Otherwise, use the _ version
419
+ send(:"_#{key}=", value)
420
+ end
421
+ end
422
+ end
423
+
424
+ # Called when something in the model changes. Saves
425
+ # the model if there is a persistor, and changes the
426
+ # model to not be new.
427
+ #
428
+ # @return [Promise|nil] a promise for when the save is
429
+ # complete
430
+ def run_changed(attribute_name=nil)
431
+ result = nil
432
+
433
+ # no_validate mode should only be used internally. no_validate mode is a
434
+ # performance optimization that prevents validation from running after each
435
+ # change when assigning multile attributes.
436
+ unless Volt.in_mode?(:no_validate)
437
+ # Run the validations for all fields
438
+ validate!
439
+
440
+ # Buffers are allowed to be in an invalid state
441
+ unless buffer?
442
+ # First check that all local validations pass
443
+ if error_in_changed_attributes?
444
+ # Some errors are present, revert changes
445
+ revert_changes!
446
+
447
+ # After we revert, we need to validate again to get the error messages back
448
+ # TODO: Could probably cache the previous errors.
449
+ errs = validate!
450
+
451
+ result = Promise.new.reject(errs)
452
+ else
453
+ # No errors, tell the persistor to handle the change (usually save)
454
+
455
+ # Don't save right now if we're in a nosave block
456
+ unless Volt.in_mode?(:no_save)
457
+ # the changed method on a persistor should return a promise that will
458
+ # be resolved when the save is complete, or fail with a hash of errors.
459
+ if @persistor
460
+ result = @persistor.changed(attribute_name)
461
+ else
462
+ result = Promise.new.resolve(nil)
463
+ end
464
+
465
+ # Saved, no longer new
466
+ @new = false
467
+
468
+ # Clear the change tracking
469
+ clear_tracked_changes!
470
+ end
471
+ end
472
+ end
473
+ end
474
+
475
+ return result
476
+ end
333
477
  end
334
478
  end
@@ -21,29 +21,39 @@ module Volt
21
21
  @persistor.event_removed(event, last, last_for_event) if @persistor
22
22
  end
23
23
 
24
- # Gets the class for a model at the specified path.
25
- def class_at_path(path)
26
- if path
27
- begin
28
- # remove the _ and then singularize
29
- if path.last == :[]
30
- index = -2
31
- else
32
- index = -1
33
- end
24
+ module ClassMethods
25
+ # Gets the class for a model at the specified path.
26
+ def class_at_path(path)
27
+ if path
28
+ begin
29
+ # remove the _ and then singularize
30
+ if path.last == :[]
31
+ index = -2
32
+ else
33
+ index = -1
34
+ end
35
+
36
+ klass_name = path[index].singularize.camelize
34
37
 
35
- klass_name = path[index].singularize.camelize
38
+ # Lookup the class
39
+ klass = Object.const_get(klass_name)
36
40
 
37
- klass = $page.model_classes[klass_name] || Model
38
- rescue NameError => e
39
- # Ignore exception, just means the model isn't defined
41
+ # Use it if it is a model
42
+ klass = Model unless klass < Model
43
+ rescue NameError => e
44
+ # Ignore exception, just means the model isn't defined
45
+ klass = Model
46
+ end
47
+ else
40
48
  klass = Model
41
49
  end
42
- else
43
- klass = Model
50
+
51
+ klass
44
52
  end
53
+ end
45
54
 
46
- klass
55
+ def self.included(base)
56
+ base.send :extend, ClassMethods
47
57
  end
48
58
  end
49
59
  end