square-activerecord 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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