activerecord 4.0.4 → 4.1.16

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. data/lib/active_record/test_case.rb +0 -96
@@ -5,11 +5,11 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
9
9
  end
10
10
 
11
- def valid_dependent_options
12
- [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
13
13
  end
14
14
  end
15
15
  end
@@ -10,16 +10,14 @@ module ActiveRecord::Associations::Builder
10
10
  valid
11
11
  end
12
12
 
13
- def constructable?
14
- !options[:through]
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
15
15
  end
16
16
 
17
- def configure_dependency
18
- super unless options[:through]
19
- end
17
+ private
20
18
 
21
- def valid_dependent_options
22
- [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
19
+ def self.add_before_destroy_callbacks(model, reflection)
20
+ super unless reflection.options[:through]
23
21
  end
24
22
  end
25
23
  end
@@ -1,19 +1,18 @@
1
+ # This class is inherited by the has_one and belongs_to association classes
2
+
1
3
  module ActiveRecord::Associations::Builder
2
4
  class SingularAssociation < Association #:nodoc:
3
5
  def valid_options
4
6
  super + [:remote, :dependent, :primary_key, :inverse_of]
5
7
  end
6
8
 
7
- def constructable?
8
- true
9
- end
10
-
11
- def define_accessors
9
+ def self.define_accessors(model, reflection)
12
10
  super
13
- define_constructors if constructable?
11
+ define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
14
12
  end
15
13
 
16
- def define_constructors
14
+ # Defines the (build|create)_association methods for belongs_to or has_one association
15
+ def self.define_constructors(mixin, name)
17
16
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
18
17
  def build_#{name}(*args, &block)
19
18
  association(:#{name}).build(*args, &block)
@@ -4,10 +4,9 @@ module ActiveRecord
4
4
  #
5
5
  # CollectionAssociation is an abstract class that provides common stuff to
6
6
  # ease the implementation of association proxies that represent
7
- # collections. See the class hierarchy in AssociationProxy.
7
+ # collections. See the class hierarchy in Association.
8
8
  #
9
9
  # CollectionAssociation:
10
- # HasAndBelongsToManyAssociation => has_and_belongs_to_many
11
10
  # HasManyAssociation => has_many
12
11
  # HasManyThroughAssociation + ThroughAssociation => has_many :through
13
12
  #
@@ -34,7 +33,13 @@ module ActiveRecord
34
33
  reload
35
34
  end
36
35
 
37
- @proxy ||= CollectionProxy.new(klass, self)
36
+ if owner.new_record?
37
+ # Cache the proxy separately before the owner has an id
38
+ # or else a post-save proxy will still lack the id
39
+ @new_record_proxy ||= CollectionProxy.create(klass, self)
40
+ else
41
+ @proxy ||= CollectionProxy.create(klass, self)
42
+ end
38
43
  end
39
44
 
40
45
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -44,7 +49,7 @@ module ActiveRecord
44
49
 
45
50
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
46
51
  def ids_reader
47
- if loaded? || options[:finder_sql]
52
+ if loaded?
48
53
  load_target.map do |record|
49
54
  record.send(reflection.association_primary_key)
50
55
  end
@@ -67,11 +72,11 @@ module ActiveRecord
67
72
  @target = []
68
73
  end
69
74
 
70
- def select(select = nil)
75
+ def select(*fields)
71
76
  if block_given?
72
77
  load_target.select.each { |e| yield e }
73
78
  else
74
- scope.select(select)
79
+ scope.select(*fields)
75
80
  end
76
81
  end
77
82
 
@@ -79,12 +84,9 @@ module ActiveRecord
79
84
  if block_given?
80
85
  load_target.find(*args) { |*block_args| yield(*block_args) }
81
86
  else
82
- if options[:finder_sql]
83
- find_by_scan(*args)
84
- elsif options[:inverse_of] && loaded?
87
+ if options[:inverse_of] && loaded?
85
88
  args_flatten = args.flatten
86
89
  raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
87
-
88
90
  result = find_by_scan(*args)
89
91
 
90
92
  result_size = Array(result).size
@@ -100,11 +102,41 @@ module ActiveRecord
100
102
  end
101
103
 
102
104
  def first(*args)
103
- first_or_last(:first, *args)
105
+ first_nth_or_last(:first, *args)
106
+ end
107
+
108
+ def second(*args)
109
+ first_nth_or_last(:second, *args)
110
+ end
111
+
112
+ def third(*args)
113
+ first_nth_or_last(:third, *args)
114
+ end
115
+
116
+ def fourth(*args)
117
+ first_nth_or_last(:fourth, *args)
118
+ end
119
+
120
+ def fifth(*args)
121
+ first_nth_or_last(:fifth, *args)
122
+ end
123
+
124
+ def forty_two(*args)
125
+ first_nth_or_last(:forty_two, *args)
104
126
  end
105
127
 
106
128
  def last(*args)
107
- first_or_last(:last, *args)
129
+ first_nth_or_last(:last, *args)
130
+ end
131
+
132
+ def take(n = nil)
133
+ if loaded?
134
+ n ? target.take(n) : target.first
135
+ else
136
+ scope.take(n).tap do |record|
137
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
138
+ end
139
+ end
108
140
  end
109
141
 
110
142
  def build(attributes = {}, &block)
@@ -118,11 +150,11 @@ module ActiveRecord
118
150
  end
119
151
 
120
152
  def create(attributes = {}, &block)
121
- create_record(attributes, &block)
153
+ _create_record(attributes, &block)
122
154
  end
123
155
 
124
156
  def create!(attributes = {}, &block)
125
- create_record(attributes, true, &block)
157
+ _create_record(attributes, true, &block)
126
158
  end
127
159
 
128
160
  # Add +records+ to this association. Returns +self+ so method calls may
@@ -153,11 +185,33 @@ module ActiveRecord
153
185
  end
154
186
  end
155
187
 
156
- # Remove all records from this association.
188
+ # Removes all records from the association without calling callbacks
189
+ # on the associated records. It honors the `:dependent` option. However
190
+ # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
191
+ # deletion strategy for the association is applied.
192
+ #
193
+ # You can force a particular deletion strategy by passing a parameter.
194
+ #
195
+ # Example:
196
+ #
197
+ # @author.books.delete_all(:nullify)
198
+ # @author.books.delete_all(:delete_all)
157
199
  #
158
200
  # See delete for more info.
159
- def delete_all
160
- delete(:all).tap do
201
+ def delete_all(dependent = nil)
202
+ if dependent.present? && ![:nullify, :delete_all].include?(dependent)
203
+ raise ArgumentError, "Valid values are :nullify or :delete_all"
204
+ end
205
+
206
+ dependent = if dependent.present?
207
+ dependent
208
+ elsif options[:dependent] == :destroy
209
+ :delete_all
210
+ else
211
+ options[:dependent]
212
+ end
213
+
214
+ delete(:all, dependent: dependent).tap do
161
215
  reset
162
216
  loaded!
163
217
  end
@@ -173,36 +227,29 @@ module ActiveRecord
173
227
  end
174
228
  end
175
229
 
176
- # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
177
- # association, it will be used for the query. Otherwise, construct options and pass them with
230
+ # Count all records using SQL. Construct options and pass them with
178
231
  # scope to the target class's +count+.
179
232
  def count(column_name = nil, count_options = {})
233
+ # TODO: Remove count_options argument as soon we remove support to
234
+ # activerecord-deprecated_finders.
180
235
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
181
236
 
182
- if options[:counter_sql] || options[:finder_sql]
183
- unless count_options.blank?
184
- raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
185
- end
186
-
187
- reflection.klass.count_by_sql(custom_counter_sql)
188
- else
189
- relation = scope
190
- if association_scope.distinct_value
191
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
192
- column_name ||= reflection.klass.primary_key
193
- relation = relation.distinct
194
- end
237
+ relation = scope
238
+ if association_scope.distinct_value
239
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
240
+ column_name ||= reflection.klass.primary_key
241
+ relation = relation.distinct
242
+ end
195
243
 
196
- value = relation.count(column_name, count_options)
244
+ value = relation.count(column_name)
197
245
 
198
- limit = options[:limit]
199
- offset = options[:offset]
246
+ limit = options[:limit]
247
+ offset = options[:offset]
200
248
 
201
- if limit || offset
202
- [ [value - offset.to_i, 0].max, limit.to_i ].min
203
- else
204
- value
205
- end
249
+ if limit || offset
250
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
251
+ else
252
+ value
206
253
  end
207
254
  end
208
255
 
@@ -214,19 +261,11 @@ module ActiveRecord
214
261
  # are actually removed from the database, that depends precisely on
215
262
  # +delete_records+. They are in any case removed from the collection.
216
263
  def delete(*records)
217
- dependent = options[:dependent]
264
+ _options = records.extract_options!
265
+ dependent = _options[:dependent] || options[:dependent]
218
266
 
219
267
  if records.first == :all
220
-
221
- if dependent && dependent == :destroy
222
- message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
223
- 'It means if the :dependent option is :destroy then the associated ' \
224
- 'records would be deleted without loading and invoking callbacks.'
225
-
226
- ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
227
- end
228
-
229
- if loaded? || dependent == :destroy
268
+ if (loaded? || dependent == :destroy) && dependent != :delete_all
230
269
  delete_or_destroy(load_target, dependent)
231
270
  else
232
271
  delete_records(:all, dependent)
@@ -237,11 +276,11 @@ module ActiveRecord
237
276
  end
238
277
  end
239
278
 
240
- # Destroy +records+ and remove them from this association calling
241
- # +before_remove+ and +after_remove+ callbacks.
279
+ # Deletes the +records+ and removes them from this association calling
280
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
242
281
  #
243
- # Note that this method will _always_ remove records from the database
244
- # ignoring the +:dependent+ option.
282
+ # Note that this method removes records from the database ignoring the
283
+ # +:dependent+ option.
245
284
  def destroy(*records)
246
285
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
247
286
  delete_or_destroy(records, :destroy)
@@ -285,14 +324,14 @@ module ActiveRecord
285
324
 
286
325
  # Returns true if the collection is empty.
287
326
  #
288
- # If the collection has been loaded or the <tt>:counter_sql</tt> option
289
- # is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the
327
+ # If the collection has been loaded
328
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
290
329
  # collection has not been loaded, it is equivalent to
291
330
  # <tt>collection.exists?</tt>. If the collection has not already been
292
331
  # loaded and you are going to fetch the records anyway it is better to
293
332
  # check <tt>collection.length.zero?</tt>.
294
333
  def empty?
295
- if loaded? || options[:counter_sql]
334
+ if loaded?
296
335
  size.zero?
297
336
  else
298
337
  @target.blank? && !scope.exists?
@@ -345,7 +384,6 @@ module ActiveRecord
345
384
  if record.new_record?
346
385
  include_in_memory?(record)
347
386
  else
348
- load_target if options[:finder_sql]
349
387
  loaded? ? target.include?(record) : scope.exists?(record)
350
388
  end
351
389
  else
@@ -362,8 +400,8 @@ module ActiveRecord
362
400
  target
363
401
  end
364
402
 
365
- def add_to_target(record)
366
- callback(:before_add, record)
403
+ def add_to_target(record, skip_callbacks = false)
404
+ callback(:before_add, record) unless skip_callbacks
367
405
  yield(record) if block_given?
368
406
 
369
407
  if association_scope.distinct_value && index = @target.index(record)
@@ -372,7 +410,7 @@ module ActiveRecord
372
410
  @target << record
373
411
  end
374
412
 
375
- callback(:after_add, record)
413
+ callback(:after_add, record) unless skip_callbacks
376
414
  set_inverse_instance(record)
377
415
 
378
416
  record
@@ -390,31 +428,8 @@ module ActiveRecord
390
428
 
391
429
  private
392
430
 
393
- def custom_counter_sql
394
- if options[:counter_sql]
395
- interpolate(options[:counter_sql])
396
- else
397
- # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
398
- interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
399
- count_with = $2.to_s
400
- count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
401
- "SELECT #{$1}COUNT(#{count_with}) FROM"
402
- end
403
- end
404
- end
405
-
406
- def custom_finder_sql
407
- interpolate(options[:finder_sql])
408
- end
409
-
410
431
  def find_target
411
- records =
412
- if options[:finder_sql]
413
- reflection.klass.find_by_sql(custom_finder_sql)
414
- else
415
- scope.to_a
416
- end
417
-
432
+ records = scope.to_a
418
433
  records.each { |record| set_inverse_instance(record) }
419
434
  records
420
435
  end
@@ -449,13 +464,13 @@ module ActiveRecord
449
464
  persisted + memory
450
465
  end
451
466
 
452
- def create_record(attributes, raise = false, &block)
467
+ def _create_record(attributes, raise = false, &block)
453
468
  unless owner.persisted?
454
469
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
455
470
  end
456
471
 
457
472
  if attributes.is_a?(Array)
458
- attributes.collect { |attr| create_record(attr, raise, &block) }
473
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
459
474
  else
460
475
  transaction do
461
476
  add_to_target(build_record(attributes)) do |record|
@@ -514,13 +529,13 @@ module ActiveRecord
514
529
  target
515
530
  end
516
531
 
517
- def concat_records(records)
532
+ def concat_records(records, should_raise = false)
518
533
  result = true
519
534
 
520
535
  records.flatten.each do |record|
521
536
  raise_on_type_mismatch!(record)
522
537
  add_to_target(record) do |rec|
523
- result &&= insert_record(rec) unless owner.new_record?
538
+ result &&= insert_record(rec, true, should_raise) unless owner.new_record?
524
539
  end
525
540
  end
526
541
 
@@ -529,20 +544,13 @@ module ActiveRecord
529
544
 
530
545
  def callback(method, record)
531
546
  callbacks_for(method).each do |callback|
532
- case callback
533
- when Symbol
534
- owner.send(callback, record)
535
- when Proc
536
- callback.call(owner, record)
537
- else
538
- callback.send(method, owner, record)
539
- end
547
+ callback.call(method, owner, record)
540
548
  end
541
549
  end
542
550
 
543
551
  def callbacks_for(callback_name)
544
552
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
545
- owner.class.send(full_callback_name.to_sym) || []
553
+ owner.class.send(full_callback_name)
546
554
  end
547
555
 
548
556
  # Should we deal with assoc.first or assoc.last by issuing an independent query to
@@ -553,33 +561,35 @@ module ActiveRecord
553
561
  # Otherwise, go to the database only if none of the following are true:
554
562
  # * target already loaded
555
563
  # * owner is new record
556
- # * custom :finder_sql exists
557
564
  # * target contains new or changed record(s)
558
- # * the first arg is an integer (which indicates the number of records to be returned)
559
- def fetch_first_or_last_using_find?(args)
565
+ def fetch_first_nth_or_last_using_find?(args)
560
566
  if args.first.is_a?(Hash)
561
567
  true
562
568
  else
563
569
  !(loaded? ||
564
570
  owner.new_record? ||
565
- options[:finder_sql] ||
566
- target.any? { |record| record.new_record? || record.changed? } ||
567
- args.first.kind_of?(Integer))
571
+ target.any? { |record| record.new_record? || record.changed? })
568
572
  end
569
573
  end
570
574
 
571
575
  def include_in_memory?(record)
572
576
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
573
- owner.send(reflection.through_reflection.name).any? { |source|
574
- target = source.send(reflection.source_reflection.name)
575
- target.respond_to?(:include?) ? target.include?(record) : target == record
577
+ assoc = owner.association(reflection.through_reflection.name)
578
+ assoc.reader.any? { |source|
579
+ target_association = source.send(reflection.source_reflection.name)
580
+
581
+ if target_association.respond_to?(:include?)
582
+ target_association.include?(record)
583
+ else
584
+ target_association == record
585
+ end
576
586
  } || target.include?(record)
577
587
  else
578
588
  target.include?(record)
579
589
  end
580
590
  end
581
591
 
582
- # If using a custom finder_sql or if the :inverse_of option has been
592
+ # If the :inverse_of option has been
583
593
  # specified, then #find scans the entire collection.
584
594
  def find_by_scan(*args)
585
595
  expects_array = args.first.kind_of?(Array)
@@ -595,10 +605,10 @@ module ActiveRecord
595
605
  end
596
606
 
597
607
  # Fetches the first/last using SQL if possible, otherwise from the target array.
598
- def first_or_last(type, *args)
608
+ def first_nth_or_last(type, *args)
599
609
  args.shift if args.first.is_a?(Hash) && args.first.empty?
600
610
 
601
- collection = fetch_first_or_last_using_find?(args) ? scope : load_target
611
+ collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
602
612
  collection.send(type, *args).tap do |record|
603
613
  set_inverse_instance record if record.is_a? ActiveRecord::Base
604
614
  end
@@ -33,7 +33,6 @@ module ActiveRecord
33
33
  def initialize(klass, association) #:nodoc:
34
34
  @association = association
35
35
  super klass, klass.arel_table
36
- self.default_scoped = true
37
36
  merge! association.scope(nullify: false)
38
37
  end
39
38
 
@@ -76,7 +75,7 @@ module ActiveRecord
76
75
  # # #<Pet id: nil, name: "Choo-Choo">
77
76
  # # ]
78
77
  #
79
- # person.pets.select([:id, :name])
78
+ # person.pets.select(:id, :name )
80
79
  # # => [
81
80
  # # #<Pet id: 1, name: "Fancy-Fancy">,
82
81
  # # #<Pet id: 2, name: "Spook">,
@@ -85,7 +84,7 @@ module ActiveRecord
85
84
  #
86
85
  # Be careful because this also means you're initializing a model
87
86
  # object with only the fields that you've selected. If you attempt
88
- # to access a field that is not in the initialized record you'll
87
+ # to access a field except +id+ that is not in the initialized record you'll
89
88
  # receive:
90
89
  #
91
90
  # person.pets.select(:name).first.person_id
@@ -107,13 +106,13 @@ module ActiveRecord
107
106
  # # #<Pet id: 2, name: "Spook">,
108
107
  # # #<Pet id: 3, name: "Choo-Choo">
109
108
  # # ]
110
- def select(select = nil, &block)
111
- @association.select(select, &block)
109
+ def select(*fields, &block)
110
+ @association.select(*fields, &block)
112
111
  end
113
112
 
114
113
  # Finds an object in the collection responding to the +id+. Uses the same
115
114
  # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
116
- # error if the object can not be found.
115
+ # error if the object cannot be found.
117
116
  #
118
117
  # class Person < ActiveRecord::Base
119
118
  # has_many :pets
@@ -171,6 +170,32 @@ module ActiveRecord
171
170
  @association.first(*args)
172
171
  end
173
172
 
173
+ # Same as +first+ except returns only the second record.
174
+ def second(*args)
175
+ @association.second(*args)
176
+ end
177
+
178
+ # Same as +first+ except returns only the third record.
179
+ def third(*args)
180
+ @association.third(*args)
181
+ end
182
+
183
+ # Same as +first+ except returns only the fourth record.
184
+ def fourth(*args)
185
+ @association.fourth(*args)
186
+ end
187
+
188
+ # Same as +first+ except returns only the fifth record.
189
+ def fifth(*args)
190
+ @association.fifth(*args)
191
+ end
192
+
193
+ # Same as +first+ except returns only the forty second record.
194
+ # Also known as accessing "the reddit".
195
+ def forty_two(*args)
196
+ @association.forty_two(*args)
197
+ end
198
+
174
199
  # Returns the last record, or the last +n+ records, from the collection.
175
200
  # If the collection is empty, the first form returns +nil+, and the second
176
201
  # form returns an empty array.
@@ -201,6 +226,10 @@ module ActiveRecord
201
226
  @association.last(*args)
202
227
  end
203
228
 
229
+ def take(n = nil)
230
+ @association.take(n)
231
+ end
232
+
204
233
  # Returns a new object of the collection type that has been instantiated
205
234
  # with +attributes+ and linked to this object, but have not yet been saved.
206
235
  # You can pass an array of attributes hashes, this will return an array
@@ -418,13 +447,13 @@ module ActiveRecord
418
447
  #
419
448
  # Pet.find(1, 2, 3)
420
449
  # # => ActiveRecord::RecordNotFound
421
- def delete_all
422
- @association.delete_all
450
+ def delete_all(dependent = nil)
451
+ @association.delete_all(dependent)
423
452
  end
424
453
 
425
- # Deletes the records of the collection directly from the database.
426
- # This will _always_ remove the records ignoring the +:dependent+
427
- # option.
454
+ # Deletes the records of the collection directly from the database
455
+ # ignoring the +:dependent+ option. It invokes +before_remove+,
456
+ # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
428
457
  #
429
458
  # class Person < ActiveRecord::Base
430
459
  # has_many :pets
@@ -671,6 +700,8 @@ module ActiveRecord
671
700
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
672
701
  # # ]
673
702
  def count(column_name = nil, options = {})
703
+ # TODO: Remove options argument as soon we remove support to
704
+ # activerecord-deprecated_finders.
674
705
  @association.count(column_name, options)
675
706
  end
676
707
 
@@ -727,7 +758,7 @@ module ActiveRecord
727
758
  end
728
759
 
729
760
  # Returns +true+ if the collection is empty. If the collection has been
730
- # loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
761
+ # loaded it is equivalent
731
762
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
732
763
  # it is equivalent to <tt>collection.exists?</tt>. If the collection has
733
764
  # not already been loaded and you are going to fetch the records anyway it
@@ -788,12 +819,12 @@ module ActiveRecord
788
819
  # has_many :pets
789
820
  # end
790
821
  #
791
- # person.pets.count #=> 1
792
- # person.pets.many? #=> false
822
+ # person.pets.count # => 1
823
+ # person.pets.many? # => false
793
824
  #
794
825
  # person.pets << Pet.new(name: 'Snoopy')
795
- # person.pets.count #=> 2
796
- # person.pets.many? #=> true
826
+ # person.pets.count # => 2
827
+ # person.pets.many? # => true
797
828
  #
798
829
  # You can also pass a block to define criteria. The
799
830
  # behavior is the same, it returns true if the collection
@@ -833,6 +864,10 @@ module ActiveRecord
833
864
  !!@association.include?(record)
834
865
  end
835
866
 
867
+ def arel
868
+ scope.arel
869
+ end
870
+
836
871
  def proxy_association
837
872
  @association
838
873
  end
@@ -849,8 +884,6 @@ module ActiveRecord
849
884
  def scope
850
885
  @association.scope
851
886
  end
852
-
853
- # :nodoc:
854
887
  alias spawn scope
855
888
 
856
889
  # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
@@ -979,6 +1012,28 @@ module ActiveRecord
979
1012
  proxy_association.reload
980
1013
  self
981
1014
  end
1015
+
1016
+ # Unloads the association. Returns +self+.
1017
+ #
1018
+ # class Person < ActiveRecord::Base
1019
+ # has_many :pets
1020
+ # end
1021
+ #
1022
+ # person.pets # fetches pets from the database
1023
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1024
+ #
1025
+ # person.pets # uses the pets cache
1026
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1027
+ #
1028
+ # person.pets.reset # clears the pets cache
1029
+ #
1030
+ # person.pets # fetches pets from the database
1031
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1032
+ def reset
1033
+ proxy_association.reset
1034
+ proxy_association.reset_scope
1035
+ self
1036
+ end
982
1037
  end
983
1038
  end
984
1039
  end