square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +359 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,572 @@
1
+ require 'set'
2
+ require 'active_support/core_ext/array/wrap'
3
+
4
+ module ActiveRecord
5
+ module Associations
6
+ # = Active Record Association Collection
7
+ #
8
+ # AssociationCollection is an abstract class that provides common stuff to
9
+ # ease the implementation of association proxies that represent
10
+ # collections. See the class hierarchy in AssociationProxy.
11
+ #
12
+ # You need to be careful with assumptions regarding the target: The proxy
13
+ # does not fetch records from the database until it needs them, but new
14
+ # ones created with +build+ are added to the target. So, the target may be
15
+ # non-empty and still lack children waiting to be read from the database.
16
+ # If you look directly to the database you cannot assume that's the entire
17
+ # collection because new records may have been added to the target, etc.
18
+ #
19
+ # If you need to work on all current children, new and existing records,
20
+ # +load_target+ and the +loaded+ flag are your friends.
21
+ class AssociationCollection < AssociationProxy #:nodoc:
22
+ def initialize(owner, reflection)
23
+ super
24
+ construct_sql
25
+ end
26
+
27
+ delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
28
+
29
+ def select(select = nil)
30
+ if block_given?
31
+ load_target
32
+ @target.select.each { |e| yield e }
33
+ else
34
+ scoped.select(select)
35
+ end
36
+ end
37
+
38
+ def scoped
39
+ with_scope(construct_scope) { @reflection.klass.scoped }
40
+ end
41
+
42
+ def find(*args)
43
+ options = args.extract_options!
44
+
45
+ # If using a custom finder_sql, scan the entire collection.
46
+ if @reflection.options[:finder_sql]
47
+ expects_array = args.first.kind_of?(Array)
48
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
49
+
50
+ if ids.size == 1
51
+ id = ids.first
52
+ record = load_target.detect { |r| id == r.id }
53
+ expects_array ? [ record ] : record
54
+ else
55
+ load_target.select { |r| ids.include?(r.id) }
56
+ end
57
+ else
58
+ merge_options_from_reflection!(options)
59
+ construct_find_options!(options)
60
+
61
+ find_scope = construct_scope[:find].slice(:conditions, :order)
62
+
63
+ with_scope(:find => find_scope) do
64
+ relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
65
+
66
+ case args.first
67
+ when :first, :last
68
+ relation.send(args.first)
69
+ when :all
70
+ records = relation.all
71
+ @reflection.options[:uniq] ? uniq(records) : records
72
+ else
73
+ relation.find(*args)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # Fetches the first one using SQL if possible.
80
+ def first(*args)
81
+ if fetch_first_or_last_using_find?(args)
82
+ find(:first, *args)
83
+ else
84
+ load_target unless loaded?
85
+ @target.first(*args)
86
+ end
87
+ end
88
+
89
+ # Fetches the last one using SQL if possible.
90
+ def last(*args)
91
+ if fetch_first_or_last_using_find?(args)
92
+ find(:last, *args)
93
+ else
94
+ load_target unless loaded?
95
+ @target.last(*args)
96
+ end
97
+ end
98
+
99
+ def to_ary
100
+ load_target
101
+ if @target.is_a?(Array)
102
+ @target.to_ary
103
+ else
104
+ Array.wrap(@target)
105
+ end
106
+ end
107
+ alias_method :to_a, :to_ary
108
+
109
+ def reset
110
+ reset_target!
111
+ reset_named_scopes_cache!
112
+ @loaded = false
113
+ end
114
+
115
+ def build(attributes = {}, &block)
116
+ if attributes.is_a?(Array)
117
+ attributes.collect { |attr| build(attr, &block) }
118
+ else
119
+ build_record(attributes) do |record|
120
+ block.call(record) if block_given?
121
+ set_belongs_to_association_for(record)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
127
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
128
+ def <<(*records)
129
+ result = true
130
+ load_target if @owner.new_record?
131
+
132
+ transaction do
133
+ flatten_deeper(records).each do |record|
134
+ raise_on_type_mismatch(record)
135
+ add_record_to_target_with_callbacks(record) do |r|
136
+ result &&= insert_record(record) unless @owner.new_record?
137
+ end
138
+ end
139
+ end
140
+
141
+ result && self
142
+ end
143
+
144
+ alias_method :push, :<<
145
+ alias_method :concat, :<<
146
+
147
+ # Starts a transaction in the association class's database connection.
148
+ #
149
+ # class Author < ActiveRecord::Base
150
+ # has_many :books
151
+ # end
152
+ #
153
+ # Author.first.books.transaction do
154
+ # # same effect as calling Book.transaction
155
+ # end
156
+ def transaction(*args)
157
+ @reflection.klass.transaction(*args) do
158
+ yield
159
+ end
160
+ end
161
+
162
+ # Remove all records from this association
163
+ #
164
+ # See delete for more info.
165
+ def delete_all
166
+ load_target
167
+ delete(@target)
168
+ reset_target!
169
+ reset_named_scopes_cache!
170
+ end
171
+
172
+ # Calculate sum using SQL, not Enumerable
173
+ def sum(*args)
174
+ if block_given?
175
+ calculate(:sum, *args) { |*block_args| yield(*block_args) }
176
+ else
177
+ calculate(:sum, *args)
178
+ end
179
+ end
180
+
181
+ # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
182
+ # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
183
+ # descendant's +construct_sql+ method will have set :counter_sql automatically.
184
+ # Otherwise, construct options and pass them with scope to the target class's +count+.
185
+ def count(column_name = nil, options = {})
186
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
187
+
188
+ if @reflection.options[:finder_sql] || @reflection.options[:counter_sql]
189
+ unless options.blank?
190
+ raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
191
+ end
192
+
193
+ @reflection.klass.count_by_sql(@counter_sql)
194
+ else
195
+
196
+ if @reflection.options[:uniq]
197
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
198
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
199
+ options.merge!(:distinct => true)
200
+ end
201
+
202
+ value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
203
+
204
+ limit = @reflection.options[:limit]
205
+ offset = @reflection.options[:offset]
206
+
207
+ if limit || offset
208
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
209
+ else
210
+ value
211
+ end
212
+ end
213
+ end
214
+
215
+ # Removes +records+ from this association calling +before_remove+ and
216
+ # +after_remove+ callbacks.
217
+ #
218
+ # This method is abstract in the sense that +delete_records+ has to be
219
+ # provided by descendants. Note this method does not imply the records
220
+ # are actually removed from the database, that depends precisely on
221
+ # +delete_records+. They are in any case removed from the collection.
222
+ def delete(*records)
223
+ remove_records(records) do |_records, old_records|
224
+ delete_records(old_records) if old_records.any?
225
+ _records.each { |record| @target.delete(record) }
226
+ end
227
+ end
228
+
229
+ # Destroy +records+ and remove them from this association calling
230
+ # +before_remove+ and +after_remove+ callbacks.
231
+ #
232
+ # Note that this method will _always_ remove records from the database
233
+ # ignoring the +:dependent+ option.
234
+ def destroy(*records)
235
+ records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
236
+ remove_records(records) do |_records, old_records|
237
+ old_records.each { |record| record.destroy }
238
+ end
239
+
240
+ load_target
241
+ end
242
+
243
+ # Removes all records from this association. Returns +self+ so method calls may be chained.
244
+ def clear
245
+ return self if length.zero? # forces load_target if it hasn't happened already
246
+
247
+ if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
248
+ destroy_all
249
+ else
250
+ delete_all
251
+ end
252
+
253
+ self
254
+ end
255
+
256
+ # Destroy all the records from this association.
257
+ #
258
+ # See destroy for more info.
259
+ def destroy_all
260
+ load_target
261
+ destroy(@target).tap do
262
+ reset_target!
263
+ reset_named_scopes_cache!
264
+ end
265
+ end
266
+
267
+ def create(attrs = {})
268
+ if attrs.is_a?(Array)
269
+ attrs.collect { |attr| create(attr) }
270
+ else
271
+ create_record(attrs) do |record|
272
+ yield(record) if block_given?
273
+ record.save
274
+ end
275
+ end
276
+ end
277
+
278
+ def create!(attrs = {})
279
+ create_record(attrs) do |record|
280
+ yield(record) if block_given?
281
+ record.save!
282
+ end
283
+ end
284
+
285
+ # Returns the size of the collection by executing a SELECT COUNT(*)
286
+ # query if the collection hasn't been loaded, and calling
287
+ # <tt>collection.size</tt> if it has.
288
+ #
289
+ # If the collection has been already loaded +size+ and +length+ are
290
+ # equivalent. If not and you are going to need the records anyway
291
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
292
+ #
293
+ # This method is abstract in the sense that it relies on
294
+ # +count_records+, which is a method descendants have to provide.
295
+ def size
296
+ if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
297
+ @target.size
298
+ elsif !loaded? && @reflection.options[:group]
299
+ load_target.size
300
+ elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
301
+ unsaved_records = @target.select { |r| r.new_record? }
302
+ unsaved_records.size + count_records
303
+ else
304
+ count_records
305
+ end
306
+ end
307
+
308
+ # Returns the size of the collection calling +size+ on the target.
309
+ #
310
+ # If the collection has been already loaded +length+ and +size+ are
311
+ # equivalent. If not and you are going to need the records anyway this
312
+ # method will take one less query. Otherwise +size+ is more efficient.
313
+ def length
314
+ load_target.size
315
+ end
316
+
317
+ # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
318
+ # not been already loaded and you are going to fetch the records anyway
319
+ # it is better to check <tt>collection.length.zero?</tt>.
320
+ def empty?
321
+ size.zero?
322
+ end
323
+
324
+ def any?
325
+ if block_given?
326
+ method_missing(:any?) { |*block_args| yield(*block_args) }
327
+ else
328
+ !empty?
329
+ end
330
+ end
331
+
332
+ # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
333
+ def many?
334
+ if block_given?
335
+ method_missing(:many?) { |*block_args| yield(*block_args) }
336
+ else
337
+ size > 1
338
+ end
339
+ end
340
+
341
+ def uniq(collection = self)
342
+ seen = Set.new
343
+ collection.map do |record|
344
+ unless seen.include?(record.id)
345
+ seen << record.id
346
+ record
347
+ end
348
+ end.compact
349
+ end
350
+
351
+ # Replace this collection with +other_array+
352
+ # This will perform a diff and delete/add only records that have changed.
353
+ def replace(other_array)
354
+ other_array.each { |val| raise_on_type_mismatch(val) }
355
+
356
+ load_target
357
+ other = other_array.size < 100 ? other_array : other_array.to_set
358
+ current = @target.size < 100 ? @target : @target.to_set
359
+
360
+ transaction do
361
+ delete(@target.select { |v| !other.include?(v) })
362
+ concat(other_array.select { |v| !current.include?(v) })
363
+ end
364
+ end
365
+
366
+ def include?(record)
367
+ return false unless record.is_a?(@reflection.klass)
368
+ return include_in_memory?(record) if record.new_record?
369
+ load_target if @reflection.options[:finder_sql] && !loaded?
370
+ return @target.include?(record) if loaded?
371
+ exists?(record)
372
+ end
373
+
374
+ def proxy_respond_to?(method, include_private = false)
375
+ super || @reflection.klass.respond_to?(method, include_private)
376
+ end
377
+
378
+ protected
379
+ def construct_find_options!(options)
380
+ end
381
+
382
+ def construct_counter_sql
383
+ if @reflection.options[:counter_sql]
384
+ @counter_sql = interpolate_and_sanitize_sql(@reflection.options[:counter_sql])
385
+ elsif @reflection.options[:finder_sql]
386
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
387
+ @counter_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
388
+ else
389
+ @counter_sql = @finder_sql
390
+ end
391
+ end
392
+
393
+ def load_target
394
+ if !@owner.new_record? || foreign_key_present
395
+ begin
396
+ if !loaded?
397
+ if @target.is_a?(Array) && @target.any?
398
+ @target = find_target.map do |f|
399
+ i = @target.index(f)
400
+ if i
401
+ @target.delete_at(i).tap do |t|
402
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
403
+ t.attributes = f.attributes.except(*keys)
404
+ end
405
+ else
406
+ f
407
+ end
408
+ end + @target
409
+ else
410
+ @target = find_target
411
+ end
412
+ end
413
+ rescue ActiveRecord::RecordNotFound
414
+ reset
415
+ end
416
+ end
417
+
418
+ loaded if target
419
+ target
420
+ end
421
+
422
+ def method_missing(method, *args)
423
+ match = DynamicFinderMatch.match(method)
424
+ if match && match.creator?
425
+ attributes = match.attribute_names
426
+ return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
427
+ end
428
+
429
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
430
+ if block_given?
431
+ super { |*block_args| yield(*block_args) }
432
+ else
433
+ super
434
+ end
435
+ elsif @reflection.klass.scopes[method]
436
+ @_named_scopes_cache ||= {}
437
+ @_named_scopes_cache[method] ||= {}
438
+ @_named_scopes_cache[method][args] ||= with_scope(construct_scope) { @reflection.klass.send(method, *args) }
439
+ else
440
+ with_scope(construct_scope) do
441
+ if block_given?
442
+ @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
443
+ else
444
+ @reflection.klass.send(method, *args)
445
+ end
446
+ end
447
+ end
448
+ end
449
+
450
+ # overloaded in derived Association classes to provide useful scoping depending on association type.
451
+ def construct_scope
452
+ {}
453
+ end
454
+
455
+ def reset_target!
456
+ @target = Array.new
457
+ end
458
+
459
+ def reset_named_scopes_cache!
460
+ @_named_scopes_cache = {}
461
+ end
462
+
463
+ def find_target
464
+ records =
465
+ if @reflection.options[:finder_sql]
466
+ @reflection.klass.find_by_sql(@finder_sql)
467
+ else
468
+ find(:all)
469
+ end
470
+
471
+ records = @reflection.options[:uniq] ? uniq(records) : records
472
+ records.each do |record|
473
+ set_inverse_instance(record, @owner)
474
+ end
475
+ records
476
+ end
477
+
478
+ def add_record_to_target_with_callbacks(record)
479
+ callback(:before_add, record)
480
+ yield(record) if block_given?
481
+ @target ||= [] unless loaded?
482
+ if index = @target.index(record)
483
+ @target[index] = record
484
+ else
485
+ @target << record
486
+ end
487
+ callback(:after_add, record)
488
+ set_inverse_instance(record, @owner)
489
+ record
490
+ end
491
+
492
+ private
493
+ def create_record(attrs)
494
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
495
+ ensure_owner_is_not_new
496
+
497
+ scoped_where = scoped.where_values_hash
498
+ create_scope = scoped_where ? construct_scope[:create].merge(scoped_where) : construct_scope[:create]
499
+ record = @reflection.klass.send(:with_scope, :create => create_scope) do
500
+ @reflection.build_association(attrs)
501
+ end
502
+ if block_given?
503
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
504
+ else
505
+ add_record_to_target_with_callbacks(record)
506
+ end
507
+ end
508
+
509
+ def build_record(attrs)
510
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
511
+ record = @reflection.build_association(attrs)
512
+ if block_given?
513
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
514
+ else
515
+ add_record_to_target_with_callbacks(record)
516
+ end
517
+ end
518
+
519
+ def remove_records(*records)
520
+ records = flatten_deeper(records)
521
+ records.each { |record| raise_on_type_mismatch(record) }
522
+
523
+ transaction do
524
+ records.each { |record| callback(:before_remove, record) }
525
+ old_records = records.reject { |r| r.new_record? }
526
+ yield(records, old_records)
527
+ records.each { |record| callback(:after_remove, record) }
528
+ end
529
+ end
530
+
531
+ def callback(method, record)
532
+ callbacks_for(method).each do |callback|
533
+ case callback
534
+ when Symbol
535
+ @owner.send(callback, record)
536
+ when Proc
537
+ callback.call(@owner, record)
538
+ else
539
+ callback.send(method, @owner, record)
540
+ end
541
+ end
542
+ end
543
+
544
+ def callbacks_for(callback_name)
545
+ full_callback_name = "#{callback_name}_for_#{@reflection.name}"
546
+ @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
547
+ end
548
+
549
+ def ensure_owner_is_not_new
550
+ if @owner.new_record?
551
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
552
+ end
553
+ end
554
+
555
+ def fetch_first_or_last_using_find?(args)
556
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
557
+ @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
558
+ end
559
+
560
+ def include_in_memory?(record)
561
+ if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
562
+ @owner.send(proxy_reflection.through_reflection.name).any? { |source|
563
+ target = source.send(proxy_reflection.source_reflection.name)
564
+ target.respond_to?(:include?) ? target.include?(record) : target == record
565
+ } || @target.include?(record)
566
+ else
567
+ @target.include?(record)
568
+ end
569
+ end
570
+ end
571
+ end
572
+ end