motion_model 0.4.1 → 0.4.2
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.
- data/CHANGELOG +16 -5
- data/Gemfile +4 -0
- data/README.md +46 -0
- data/Rakefile +4 -1
- data/lib/motion_model/adapters/array_model_adapter.rb +52 -28
- data/lib/motion_model/adapters/array_model_persistence.rb +141 -0
- data/lib/motion_model/ext.rb +17 -0
- data/lib/motion_model/model/array_finder_query.rb +6 -1
- data/lib/motion_model/model/column.rb +24 -8
- data/lib/motion_model/model/formotion.rb +4 -4
- data/lib/motion_model/model/model.rb +360 -92
- data/lib/motion_model/model/model_casts.rb +13 -8
- data/lib/motion_model/model/transaction.rb +1 -1
- data/lib/motion_model/validatable.rb +29 -15
- data/lib/motion_model/version.rb +1 -1
- data/spec/adapter_spec.rb +30 -0
- data/spec/array_model_persistence_spec.rb +229 -0
- data/spec/cascading_delete_spec.rb +5 -5
- data/spec/date_spec.rb +26 -13
- data/spec/finder_spec.rb +1 -0
- data/spec/formotion_spec.rb +1 -0
- data/spec/model_hook_spec.rb +5 -4
- data/spec/model_spec.rb +1 -7
- data/spec/relation_spec.rb +10 -13
- metadata +66 -49
@@ -41,26 +41,31 @@ module MotionModel
|
|
41
41
|
class PersistFileError < Exception; end
|
42
42
|
class RelationIsNilError < Exception; end
|
43
43
|
class AdapterNotFoundError < Exception; end
|
44
|
+
class RecordNotSaved < Exception; end
|
44
45
|
|
45
46
|
module Model
|
46
47
|
def self.included(base)
|
47
48
|
base.extend(PrivateClassMethods)
|
48
49
|
base.extend(PublicClassMethods)
|
49
|
-
base.instance_variable_set("@_columns", []) # Columns in model
|
50
|
-
base.instance_variable_set("@_column_hashes", {}) # Hashes to for quick column lookup
|
51
|
-
base.instance_variable_set("@_relations", {}) # relations
|
52
|
-
base.instance_variable_set("@_next_id", 1) # Next assignable id
|
53
|
-
base.instance_variable_set("@_issue_notifications", true) # Next assignable id
|
54
50
|
end
|
55
51
|
|
56
52
|
module PublicClassMethods
|
53
|
+
|
54
|
+
def new(options = {})
|
55
|
+
object_class = options[:inheritance_type] ? Kernel.const_get(options[:inheritance_type]) : self
|
56
|
+
object_class.alloc.instance_eval do
|
57
|
+
initialize(options)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
57
62
|
# Use to do bulk insertion, updating, or deleting without
|
58
63
|
# making repeated calls to a delegate. E.g., when syncing
|
59
64
|
# with an external data source.
|
60
65
|
def bulk_update(&block)
|
61
|
-
|
66
|
+
self._issue_notifications = false
|
62
67
|
class_eval &block
|
63
|
-
|
68
|
+
self._issue_notifications = true
|
64
69
|
end
|
65
70
|
|
66
71
|
# Macro to define names and types of columns. It can be used in one of
|
@@ -79,7 +84,7 @@ module MotionModel
|
|
79
84
|
# columns :name, :age, :hobby
|
80
85
|
|
81
86
|
def columns(*fields)
|
82
|
-
return
|
87
|
+
return _columns.map{|c| c.name} if fields.empty?
|
83
88
|
|
84
89
|
case fields.first
|
85
90
|
when Hash
|
@@ -90,7 +95,7 @@ module MotionModel
|
|
90
95
|
raise ArgumentError.new("arguments to `columns' must be a symbol, a hash, or a hash of hashes -- was #{fields.first}.")
|
91
96
|
end
|
92
97
|
|
93
|
-
unless
|
98
|
+
unless columns.include?(:id)
|
94
99
|
add_field(:id, :integer)
|
95
100
|
end
|
96
101
|
end
|
@@ -120,6 +125,11 @@ module MotionModel
|
|
120
125
|
add_field relation, :has_many, options # Relation must be plural
|
121
126
|
end
|
122
127
|
|
128
|
+
def has_one(relation, options = {})
|
129
|
+
raise ArgumentError.new("arguments to has_one must be a symbol or string.") unless [Symbol, String].include? relation.class
|
130
|
+
add_field relation, :has_one, options # Relation must be plural
|
131
|
+
end
|
132
|
+
|
123
133
|
def generate_belongs_to_id(relation)
|
124
134
|
(relation.to_s.singularize.underscore + '_id').to_sym
|
125
135
|
end
|
@@ -135,26 +145,43 @@ module MotionModel
|
|
135
145
|
# Allows code like this:
|
136
146
|
#
|
137
147
|
# Assignee.find(:assignee_name).like('smith').first.task
|
138
|
-
def belongs_to(relation)
|
139
|
-
add_field relation, :belongs_to
|
140
|
-
add_field generate_belongs_to_id(relation), :belongs_to_id # a relation is singular.
|
148
|
+
def belongs_to(relation, options = {})
|
149
|
+
add_field relation, :belongs_to, options
|
141
150
|
end
|
142
151
|
|
143
152
|
# Returns true if a column exists on this model, otherwise false.
|
144
153
|
def column?(column)
|
145
|
-
|
154
|
+
!column_named(column).nil?
|
146
155
|
end
|
147
156
|
|
148
157
|
# Returns type of this column.
|
149
|
-
def
|
158
|
+
def column_type(column)
|
150
159
|
column_named(column).type || nil
|
151
160
|
end
|
152
161
|
|
162
|
+
def has_many_columns
|
163
|
+
_column_hashes.select { |name, col| col.type == :has_many}
|
164
|
+
end
|
165
|
+
|
166
|
+
def has_one_columns
|
167
|
+
_column_hashes.select { |name, col| col.type == :has_one}
|
168
|
+
end
|
169
|
+
|
170
|
+
def belongs_to_columns
|
171
|
+
_column_hashes.select { |name, col| col.type == :belongs_to}
|
172
|
+
end
|
173
|
+
|
174
|
+
def association_columns
|
175
|
+
_column_hashes.select { |name, col| [:belongs_to, :has_many, :has_one].include?(col.type)}
|
176
|
+
end
|
177
|
+
|
153
178
|
# returns default value for this column or nil.
|
154
179
|
def default(column)
|
155
|
-
column_named(column)
|
180
|
+
col = column_named(column)
|
181
|
+
col.nil? ? nil : col.default
|
156
182
|
end
|
157
183
|
|
184
|
+
# Build an instance that represents a saved object from the persistence layer.
|
158
185
|
def read(attrs)
|
159
186
|
new(attrs).instance_eval do
|
160
187
|
@new_record = false
|
@@ -163,6 +190,12 @@ module MotionModel
|
|
163
190
|
end
|
164
191
|
end
|
165
192
|
|
193
|
+
def create!(options)
|
194
|
+
result = create(options)
|
195
|
+
raise RecordNotSaved unless result
|
196
|
+
result
|
197
|
+
end
|
198
|
+
|
166
199
|
# Creates an object and saves it. E.g.:
|
167
200
|
#
|
168
201
|
# @bob = Person.create(:name => 'Bob', :hobby => 'Bird Watching')
|
@@ -176,12 +209,12 @@ module MotionModel
|
|
176
209
|
|
177
210
|
# Destroys all rows in the model -- before_delete and after_delete
|
178
211
|
# hooks are called and deletes are not cascading if declared with
|
179
|
-
# :
|
212
|
+
# :dependent => :destroy in the has_many macro.
|
180
213
|
def destroy_all
|
181
214
|
ids = self.all.map{|item| item.id}
|
182
215
|
bulk_update do
|
183
216
|
ids.each do |item|
|
184
|
-
|
217
|
+
find_by_id(item).destroy
|
185
218
|
end
|
186
219
|
end
|
187
220
|
# Note collection is not emptied, and next_id is not reset.
|
@@ -208,6 +241,27 @@ module MotionModel
|
|
208
241
|
end
|
209
242
|
|
210
243
|
module PrivateClassMethods
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
# Hashes to for quick column lookup
|
248
|
+
def _column_hashes
|
249
|
+
@_column_hashes ||= {}
|
250
|
+
end
|
251
|
+
|
252
|
+
@_issue_notifications = true
|
253
|
+
def _issue_notifications
|
254
|
+
@_issue_notifications
|
255
|
+
end
|
256
|
+
|
257
|
+
def _issue_notifications=(value)
|
258
|
+
@_issue_notifications = value
|
259
|
+
end
|
260
|
+
|
261
|
+
def _columns
|
262
|
+
_column_hashes.values
|
263
|
+
end
|
264
|
+
|
211
265
|
# This populates a column from something like:
|
212
266
|
#
|
213
267
|
# columns :name => :string, :age => :integer
|
@@ -242,65 +296,168 @@ module MotionModel
|
|
242
296
|
end
|
243
297
|
|
244
298
|
def issue_notification(object, info) #nodoc
|
245
|
-
if
|
299
|
+
if _issue_notifications == true && !object.nil?
|
246
300
|
NSNotificationCenter.defaultCenter.postNotificationName('MotionModelDataDidChangeNotification', object: object, userInfo: info)
|
247
301
|
end
|
248
302
|
end
|
249
303
|
|
250
|
-
def define_accessor_methods(name) #nodoc
|
251
|
-
|
252
|
-
|
253
|
-
|
304
|
+
def define_accessor_methods(name, type, options = {}) #nodoc
|
305
|
+
unless alloc.respond_to?(name.to_sym)
|
306
|
+
define_method(name.to_sym) {
|
307
|
+
return nil if @data[name].nil?
|
308
|
+
if options[:symbolize]
|
309
|
+
@data[name].to_sym
|
310
|
+
else
|
311
|
+
@data[name]
|
312
|
+
end
|
313
|
+
}
|
314
|
+
end
|
254
315
|
define_method("#{name}=".to_sym) { |value|
|
255
|
-
@data[name]
|
256
|
-
|
316
|
+
old_value = @data[name]
|
317
|
+
new_value = cast_to_type(name, value)
|
318
|
+
if new_value != old_value
|
319
|
+
@data[name] = new_value
|
320
|
+
@dirty = true
|
321
|
+
end
|
257
322
|
}
|
258
323
|
end
|
259
324
|
|
260
325
|
def define_belongs_to_methods(name) #nodoc
|
326
|
+
col = column_named(name)
|
327
|
+
|
261
328
|
define_method(name) {
|
262
|
-
|
263
|
-
|
264
|
-
|
329
|
+
return @data[name] if @data[name]
|
330
|
+
if col.options[:polymorphic]
|
331
|
+
if (owner_class_name = send("#{name}_type"))
|
332
|
+
owner_class = Kernel::deep_const_get(owner_class_name.classify)
|
333
|
+
parent_id = send("#{name}_id")
|
334
|
+
end
|
335
|
+
else
|
336
|
+
owner_class = col.classify
|
337
|
+
parent_id = send(self.class.generate_belongs_to_id(col.name))
|
338
|
+
end
|
339
|
+
parent_id.nil? ? nil : owner_class.find_by_id(parent_id)
|
340
|
+
}
|
341
|
+
|
342
|
+
define_method("#{name}_relation") {
|
343
|
+
relation_for(name)
|
265
344
|
}
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
345
|
+
|
346
|
+
# Associate the parent and delegate the inverse assignment
|
347
|
+
define_method("#{name}=") { |parent|
|
348
|
+
rebuild_relation_for(name, parent)
|
349
|
+
send("set_#{name}", parent)
|
350
|
+
if col.options[:polymorphic]
|
351
|
+
foreign_column_name = parent.column_as_name(col.options[:as] || col.name)
|
352
|
+
else
|
353
|
+
foreign_column_name = self.class.name.underscore.to_sym
|
354
|
+
end
|
355
|
+
parent.rebuild_relation_for(foreign_column_name, self) if parent
|
356
|
+
}
|
357
|
+
|
358
|
+
# Associate the parent but without delegating the inverse assignment
|
359
|
+
define_method("set_#{name}") { |parent|
|
360
|
+
@data[name] = parent
|
361
|
+
if col.options[:polymorphic]
|
362
|
+
send("#{name}_type=", parent.class.name)
|
363
|
+
send("#{name}_id=", parent.id)
|
364
|
+
else
|
365
|
+
parent_id_name = self.class.generate_belongs_to_id(col.name)
|
366
|
+
send("#{parent_id_name}=", parent ? parent.id : nil)
|
367
|
+
end
|
270
368
|
}
|
369
|
+
|
370
|
+
# TODO also define #{name}+id= methods....
|
371
|
+
|
372
|
+
if col.options[:polymorphic]
|
373
|
+
add_field "#{name}_type", :belongs_to_type
|
374
|
+
add_field "#{name}_id", :belongs_to_id
|
375
|
+
else
|
376
|
+
add_field generate_belongs_to_id(name), :belongs_to_id # a relation is singular.
|
377
|
+
end
|
271
378
|
end
|
272
379
|
|
273
380
|
def define_has_many_methods(name) #nodoc
|
381
|
+
col = column_named(name)
|
382
|
+
|
383
|
+
define_method("#{name}_relation") {
|
384
|
+
relation_for(name)
|
385
|
+
}
|
386
|
+
|
274
387
|
define_method(name) {
|
388
|
+
send("#{name}_relation").to_a
|
389
|
+
}
|
390
|
+
|
391
|
+
define_method("#{name}=") do |collection|
|
392
|
+
rebuild_relation_for(name, collection)
|
393
|
+
collection.each do |instance|
|
394
|
+
if col.options[:polymorphic]
|
395
|
+
foreign_column_name = col.options[:as] || col.name
|
396
|
+
else
|
397
|
+
foreign_column_name = self.class.name.underscore.to_sym
|
398
|
+
end
|
399
|
+
instance.send("set_#{foreign_column_name}", self)
|
400
|
+
instance.rebuild_relation_for(foreign_column_name, self)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
def define_has_one_methods(name) #nodoc
|
407
|
+
col = column_named(name)
|
408
|
+
|
409
|
+
define_method("#{name}_relation") {
|
275
410
|
relation_for(name)
|
276
411
|
}
|
412
|
+
|
413
|
+
define_method(name) {
|
414
|
+
send("#{name}_relation").instance
|
415
|
+
}
|
416
|
+
|
417
|
+
define_method("#{name}=") do |instance|
|
418
|
+
relation_for(name).instance = instance
|
419
|
+
if instance
|
420
|
+
if col.options[:polymorphic]
|
421
|
+
foreign_column_name = col.options[:as] || col.name
|
422
|
+
else
|
423
|
+
foreign_column_name = self.class.name.underscore.to_sym
|
424
|
+
end
|
425
|
+
instance.rebuild_relation_for(foreign_column_name, self)
|
426
|
+
end
|
427
|
+
end
|
277
428
|
end
|
278
429
|
|
279
430
|
def add_field(name, type, options = {:default => nil}) #nodoc
|
280
431
|
col = Column.new(name, type, options)
|
281
432
|
|
282
|
-
|
283
|
-
@_column_hashes[col.name.to_sym] = col
|
433
|
+
_column_hashes[col.name.to_sym] = col
|
284
434
|
|
285
435
|
case type
|
286
|
-
when :has_many
|
287
|
-
when :
|
288
|
-
|
289
|
-
|
436
|
+
when :has_many then define_has_many_methods(name)
|
437
|
+
when :has_one then define_has_one_methods(name)
|
438
|
+
when :belongs_to then define_belongs_to_methods(name)
|
439
|
+
else define_accessor_methods(name, type, options)
|
290
440
|
end
|
291
441
|
end
|
292
442
|
|
293
443
|
# Returns a column denoted by +name+
|
294
444
|
def column_named(name) #nodoc
|
295
|
-
|
445
|
+
_column_hashes[name.to_sym]
|
296
446
|
end
|
297
447
|
|
448
|
+
# Returns the column that has the name as its :as option
|
449
|
+
def column_as(name) #nodoc
|
450
|
+
_column_hashes.values.find{ |c| c.options[:as] == name }
|
451
|
+
end
|
452
|
+
|
453
|
+
# All relation columns, including type and id columns for polymorphic associations
|
298
454
|
def relation_column?(column) #nodoc
|
299
|
-
[:belongs_to, :belongs_to_id, :has_many].include? column_named(column).type
|
455
|
+
[:belongs_to, :belongs_to_id, :belongs_to_type, :has_many, :has_one].include? column_named(column).type
|
300
456
|
end
|
301
457
|
|
302
|
-
|
303
|
-
|
458
|
+
# Polymorphic association columns that are not stored in DB
|
459
|
+
def virtual_polymorphic_relation_column?(column) #nodoc
|
460
|
+
[:belongs_to, :has_many, :has_one].include? column_named(column).type
|
304
461
|
end
|
305
462
|
|
306
463
|
def has_relation?(col) #nodoc
|
@@ -312,7 +469,7 @@ module MotionModel
|
|
312
469
|
else
|
313
470
|
column_named(col)
|
314
471
|
end
|
315
|
-
|
472
|
+
[:has_many, :has_one, :belongs_to].include?(col.type)
|
316
473
|
end
|
317
474
|
|
318
475
|
end
|
@@ -321,24 +478,73 @@ module MotionModel
|
|
321
478
|
raise AdapterNotFoundError.new("You must specify a persistence adapter.") unless self.respond_to? :adapter
|
322
479
|
|
323
480
|
@data ||= {}
|
324
|
-
before_initialize(options)
|
481
|
+
before_initialize(options) if respond_to?(:before_initialize)
|
325
482
|
|
483
|
+
# Gather defaults
|
326
484
|
columns.each do |col|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
485
|
+
next if options.has_key?(col)
|
486
|
+
next if relation_column?(col)
|
487
|
+
default = self.class.default(col)
|
488
|
+
options[col] = default unless default.nil?
|
489
|
+
end
|
490
|
+
|
491
|
+
options.each do |col, value|
|
492
|
+
initialize_data_columns col, value
|
332
493
|
end
|
333
494
|
|
334
495
|
@dirty = true
|
335
496
|
@new_record = true
|
336
497
|
end
|
337
498
|
|
499
|
+
# String uniquely identifying a saved model instance in memory
|
500
|
+
def object_identifier
|
501
|
+
["#{self.class.name}", (id.nil? ? nil : "##{id}"), ":0x#{self.object_id.to_s(16)}"].join
|
502
|
+
end
|
503
|
+
|
504
|
+
# String uniquely identifying a saved model instance
|
505
|
+
def model_identifier
|
506
|
+
raise 'Invalid' unless id
|
507
|
+
"#{self.class.name}##{id}"
|
508
|
+
end
|
509
|
+
|
338
510
|
def new_record?
|
339
511
|
@new_record
|
340
512
|
end
|
341
513
|
|
514
|
+
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
515
|
+
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
|
516
|
+
#
|
517
|
+
# Note that new records are different from any other record by definition, unless the
|
518
|
+
# other record is the receiver itself. Besides, if you fetch existing records with
|
519
|
+
# +select+ and leave the ID out, you're on your own, this predicate will return false.
|
520
|
+
#
|
521
|
+
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
522
|
+
# models are still comparable.
|
523
|
+
def ==(comparison_object)
|
524
|
+
super ||
|
525
|
+
comparison_object.instance_of?(self.class) &&
|
526
|
+
id.present? &&
|
527
|
+
comparison_object.id == id
|
528
|
+
end
|
529
|
+
alias :eql? :==
|
530
|
+
|
531
|
+
def attributes
|
532
|
+
@data
|
533
|
+
end
|
534
|
+
|
535
|
+
def attributes=(attrs)
|
536
|
+
attrs.each { |k, v| send("#{k}=", v) }
|
537
|
+
end
|
538
|
+
|
539
|
+
def update_attributes(attrs)
|
540
|
+
self.attributes = attrs
|
541
|
+
save
|
542
|
+
end
|
543
|
+
|
544
|
+
def read_attribute(name)
|
545
|
+
@data[name]
|
546
|
+
end
|
547
|
+
|
342
548
|
# Default to_i implementation returns value of id column, much as
|
343
549
|
# in Rails.
|
344
550
|
|
@@ -352,37 +558,52 @@ module MotionModel
|
|
352
558
|
columns.each{|c| "#{c}: #{self.send(c)}\n"}
|
353
559
|
end
|
354
560
|
|
561
|
+
def save!(options = {})
|
562
|
+
result = save(options)
|
563
|
+
raise RecordNotSaved unless result
|
564
|
+
result
|
565
|
+
end
|
566
|
+
|
355
567
|
# Save current object. Speaking from the context of relational
|
356
568
|
# databases, this inserts a row if it's a new one, or updates
|
357
569
|
# in place if not.
|
358
|
-
def save(
|
570
|
+
def save(options = {})
|
571
|
+
save_without_transaction(options)
|
572
|
+
end
|
573
|
+
|
574
|
+
# Performs the save.
|
575
|
+
# This is separated to allow #save to do any transaction handling that might be necessary.
|
576
|
+
def save_without_transaction(options = {})
|
577
|
+
return false if @deleted
|
359
578
|
call_hooks 'save' do
|
360
579
|
# Existing object implies update in place
|
361
580
|
action = 'add'
|
362
|
-
set_auto_date_field '
|
363
|
-
if
|
364
|
-
|
581
|
+
set_auto_date_field 'updated_at'
|
582
|
+
if new_record?
|
583
|
+
set_auto_date_field 'created_at'
|
584
|
+
result = do_insert(options)
|
365
585
|
else
|
366
|
-
do_update
|
367
|
-
set_auto_date_field 'updated_at'
|
586
|
+
result = do_update(options)
|
368
587
|
action = 'update'
|
369
588
|
end
|
370
589
|
@new_record = false
|
371
590
|
@dirty = false
|
372
|
-
|
591
|
+
issue_notification(:action => action)
|
592
|
+
result
|
373
593
|
end
|
374
594
|
end
|
375
595
|
|
376
596
|
# Set created_at and updated_at fields
|
377
597
|
def set_auto_date_field(field_name)
|
378
|
-
|
598
|
+
method = "#{field_name}="
|
599
|
+
self.send(method, Time.now) if self.respond_to?(method)
|
379
600
|
end
|
380
601
|
|
381
602
|
# Stub methods for hook protocols
|
382
|
-
def before_save(
|
383
|
-
def after_save(
|
384
|
-
def before_delete(
|
385
|
-
def after_delete(
|
603
|
+
def before_save(sender); end
|
604
|
+
def after_save(sender); end
|
605
|
+
def before_delete(sender); end
|
606
|
+
def after_delete(sender); end
|
386
607
|
|
387
608
|
def call_hook(hook_name, postfix)
|
388
609
|
hook = "#{hook_name}_#{postfix}"
|
@@ -392,33 +613,50 @@ module MotionModel
|
|
392
613
|
def call_hooks(hook_name, &block)
|
393
614
|
result = call_hook('before', hook_name)
|
394
615
|
# returning false from a before_ hook stops the process
|
395
|
-
block.call if result != false && block_given?
|
396
|
-
call_hook('after', hook_name)
|
616
|
+
result = block.call if result != false && block_given?
|
617
|
+
call_hook('after', hook_name) if result
|
618
|
+
result
|
397
619
|
end
|
398
620
|
|
399
|
-
def delete
|
400
|
-
|
621
|
+
def delete(options = {})
|
622
|
+
return if @deleted
|
623
|
+
call_hooks('delete') do
|
624
|
+
options = options.dup
|
625
|
+
options[:omit_model_identifiers] ||= {}
|
626
|
+
options[:omit_model_identifiers][model_identifier] = self
|
627
|
+
do_delete
|
628
|
+
@deleted = true
|
629
|
+
end
|
401
630
|
end
|
402
631
|
|
403
632
|
# Destroys the current object. The difference between delete
|
404
633
|
# and destroy is that destroy calls <tt>before_delete</tt>
|
405
634
|
# and <tt>after_delete</tt> hooks. As well, it will cascade
|
406
635
|
# into related objects, deleting them if they are related
|
407
|
-
# using <tt>:
|
408
|
-
#
|
636
|
+
# using <tt>:dependent => :destroy</tt> in the <tt>has_many</tt>
|
637
|
+
# and <tt>has_one></tt> declarations
|
409
638
|
#
|
410
639
|
# Note: lifecycle hooks are only called when individual objects
|
411
640
|
# are deleted.
|
412
|
-
def destroy
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
641
|
+
def destroy(options = {})
|
642
|
+
call_hooks 'destroy' do
|
643
|
+
options = options.dup
|
644
|
+
options[:omit_model_identifiers] ||= {}
|
645
|
+
options[:omit_model_identifiers][model_identifier] = self
|
646
|
+
self.class.association_columns.each do |name, col|
|
647
|
+
delete_candidates = self.send(name)
|
648
|
+
Array(delete_candidates).each do |candidate|
|
649
|
+
next if options[:omit_model_identifiers][candidate.model_identifier]
|
650
|
+
if col.dependent == :destroy
|
651
|
+
candidate.destroy(options)
|
652
|
+
elsif col.dependent == :delete
|
653
|
+
candidate.delete(options)
|
654
|
+
end
|
655
|
+
end
|
419
656
|
end
|
657
|
+
delete
|
420
658
|
end
|
421
|
-
|
659
|
+
self
|
422
660
|
end
|
423
661
|
|
424
662
|
# True if the column exists, otherwise false
|
@@ -432,8 +670,8 @@ module MotionModel
|
|
432
670
|
end
|
433
671
|
|
434
672
|
# Type of a given column
|
435
|
-
def
|
436
|
-
self.class.
|
673
|
+
def column_type(column_name)
|
674
|
+
self.class.column_type(column_name)
|
437
675
|
end
|
438
676
|
|
439
677
|
# Options hash for column, excluding the core
|
@@ -452,35 +690,65 @@ module MotionModel
|
|
452
690
|
@dirty
|
453
691
|
end
|
454
692
|
|
693
|
+
def set_dirty
|
694
|
+
@dirty = true
|
695
|
+
end
|
696
|
+
|
697
|
+
def column_as_name(name) #nodoc
|
698
|
+
self.class.send(:column_as, name.to_sym).try(:name)
|
699
|
+
end
|
700
|
+
|
455
701
|
private
|
456
702
|
|
457
|
-
def
|
458
|
-
|
703
|
+
def _column_hashes
|
704
|
+
self.class.send(:_column_hashes)
|
705
|
+
end
|
706
|
+
|
707
|
+
def relation_column?(col)
|
708
|
+
self.class.send(:relation_column?, col)
|
709
|
+
end
|
710
|
+
|
711
|
+
def virtual_polymorphic_relation_column?(col)
|
712
|
+
self.class.send(:virtual_polymorphic_relation_column?, col)
|
713
|
+
end
|
714
|
+
|
715
|
+
def has_relation?(col) #nodoc
|
716
|
+
self.class.send(:has_relation?, col)
|
717
|
+
end
|
718
|
+
|
719
|
+
def initialize_data_columns(column, value) #nodoc
|
720
|
+
self.attributes = {column => value || self.class.default(column)}
|
459
721
|
end
|
460
722
|
|
461
723
|
def column_named(name) #nodoc
|
462
|
-
self.class.column_named
|
724
|
+
self.class.send(:column_named, name.to_sym)
|
463
725
|
end
|
464
726
|
|
465
|
-
def
|
466
|
-
|
727
|
+
def column_as(name) #nodoc
|
728
|
+
self.class.send(:column_as, name.to_sym)
|
467
729
|
end
|
468
730
|
|
469
|
-
|
731
|
+
def generate_belongs_to_id(class_or_column) # nodoc
|
470
732
|
self.class.generate_belongs_to_id(self.class)
|
471
733
|
end
|
472
734
|
|
473
|
-
def
|
474
|
-
|
475
|
-
|
735
|
+
def issue_notification(info) #nodoc
|
736
|
+
self.class.send(:issue_notification, self, info)
|
737
|
+
end
|
476
738
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
739
|
+
def method_missing(sym, *args, &block)
|
740
|
+
if sym.to_s[-1] == '='
|
741
|
+
@data["#{sym.to_s.chop}".to_sym] = args.first
|
742
|
+
return args.first
|
743
|
+
else
|
744
|
+
return @data[sym] if @data && @data.has_key?(sym)
|
745
|
+
end
|
746
|
+
begin
|
747
|
+
r = super
|
748
|
+
rescue NoMethodError => exc
|
749
|
+
unless exc.to_s =~ /undefined method `(?:before|after)_/
|
750
|
+
raise
|
751
|
+
end
|
484
752
|
end
|
485
753
|
end
|
486
754
|
|