activerecord 4.2.11.3 → 5.0.7.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -28,18 +28,19 @@ module ActiveRecord
28
28
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
29
29
  def reader(force_reload = false)
30
30
  if force_reload
31
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
32
+ Passing an argument to force an association to reload is now
33
+ deprecated and will be removed in Rails 5.1. Please call `reload`
34
+ on the result collection proxy instead.
35
+ MSG
36
+
31
37
  klass.uncached { reload }
32
38
  elsif stale_target?
33
39
  reload
34
40
  end
35
41
 
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
42
+ @proxy ||= CollectionProxy.create(klass, self)
43
+ @proxy.reset_scope
43
44
  end
44
45
 
45
46
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -54,27 +55,28 @@ module ActiveRecord
54
55
  record.send(reflection.association_primary_key)
55
56
  end
56
57
  else
57
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
58
- scope.pluck(column)
58
+ @association_ids ||= (
59
+ column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
60
+ scope.pluck(column)
61
+ )
59
62
  end
60
63
  end
61
64
 
62
65
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
66
  def ids_writer(ids)
64
- pk_column = reflection.association_primary_key
65
- pk_type = klass.type_for_attribute(pk_column)
66
- ids = Array(ids).reject(&:blank?).map do |i|
67
- pk_type.type_cast_from_user(i)
68
- end
67
+ pk_type = reflection.association_primary_key_type
68
+ ids = Array(ids).reject(&:blank?)
69
+ ids.map! { |i| pk_type.cast(i) }
69
70
 
70
- objs = klass.where(pk_column => ids).index_by do |r|
71
- r.send(pk_column)
71
+ primary_key = reflection.association_primary_key
72
+ records = klass.where(primary_key => ids).index_by do |r|
73
+ r.public_send(primary_key)
72
74
  end.values_at(*ids).compact
73
75
 
74
- if objs.size == ids.size
75
- replace(objs.index_by { |r| r.send(pk_column) }.values_at(*ids))
76
+ if records.size != ids.size
77
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key)
76
78
  else
77
- klass.all.raise_record_not_found_exception!(ids, objs.size, ids.size)
79
+ replace(records)
78
80
  end
79
81
  end
80
82
 
@@ -136,6 +138,14 @@ module ActiveRecord
136
138
  first_nth_or_last(:forty_two, *args)
137
139
  end
138
140
 
141
+ def third_to_last(*args)
142
+ first_nth_or_last(:third_to_last, *args)
143
+ end
144
+
145
+ def second_to_last(*args)
146
+ first_nth_or_last(:second_to_last, *args)
147
+ end
148
+
139
149
  def last(*args)
140
150
  first_nth_or_last(:last, *args)
141
151
  end
@@ -172,6 +182,7 @@ module ActiveRecord
172
182
  # be chained. Since << flattens its argument list and inserts each record,
173
183
  # +push+ and +concat+ behave identically.
174
184
  def concat(*records)
185
+ records = records.flatten
175
186
  if owner.new_record?
176
187
  load_target
177
188
  concat_records(records)
@@ -239,11 +250,7 @@ module ActiveRecord
239
250
 
240
251
  # Count all records using SQL. Construct options and pass them with
241
252
  # scope to the target class's +count+.
242
- def count(column_name = nil, count_options = {})
243
- # TODO: Remove count_options argument as soon we remove support to
244
- # activerecord-deprecated_finders.
245
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
246
-
253
+ def count(column_name = nil)
247
254
  relation = scope
248
255
  if association_scope.distinct_value
249
256
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -307,10 +314,10 @@ module ActiveRecord
307
314
  else
308
315
  target.size
309
316
  end
310
- elsif !loaded? && !association_scope.group_values.empty?
317
+ elsif !association_scope.group_values.empty?
311
318
  load_target.size
312
- elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
313
- unsaved_records = target.select { |r| r.new_record? }
319
+ elsif !association_scope.distinct_value && target.is_a?(Array)
320
+ unsaved_records = target.select(&:new_record?)
314
321
  unsaved_records.size + count_records
315
322
  else
316
323
  count_records
@@ -343,7 +350,8 @@ module ActiveRecord
343
350
  end
344
351
 
345
352
  # Returns true if the collections is not empty.
346
- # Equivalent to +!collection.empty?+.
353
+ # If block given, loads all records and checks for one or more matches.
354
+ # Otherwise, equivalent to +!collection.empty?+.
347
355
  def any?
348
356
  if block_given?
349
357
  load_target.any? { |*block_args| yield(*block_args) }
@@ -353,7 +361,8 @@ module ActiveRecord
353
361
  end
354
362
 
355
363
  # Returns true if the collection has more than 1 record.
356
- # Equivalent to +collection.size > 1+.
364
+ # If block given, loads all records and checks for two or more matches.
365
+ # Otherwise, equivalent to +collection.size > 1+.
357
366
  def many?
358
367
  if block_given?
359
368
  load_target.many? { |*block_args| yield(*block_args) }
@@ -382,6 +391,8 @@ module ActiveRecord
382
391
  replace_common_records_in_memory(other_array, original_target)
383
392
  if other_array != original_target
384
393
  transaction { replace_records(other_array, original_target) }
394
+ else
395
+ other_array
385
396
  end
386
397
  end
387
398
  end
@@ -414,25 +425,9 @@ module ActiveRecord
414
425
  replace_on_target(record, index, skip_callbacks, &block)
415
426
  end
416
427
 
417
- def replace_on_target(record, index, skip_callbacks)
418
- callback(:before_add, record) unless skip_callbacks
419
- yield(record) if block_given?
420
-
421
- if index
422
- @target[index] = record
423
- else
424
- @target << record
425
- end
426
-
427
- callback(:after_add, record) unless skip_callbacks
428
- set_inverse_instance(record)
429
-
430
- record
431
- end
432
-
433
- def scope(opts = {})
434
- scope = super()
435
- scope.none! if opts.fetch(:nullify, true) && null_scope?
428
+ def scope
429
+ scope = super
430
+ scope.none! if null_scope?
436
431
  scope
437
432
  end
438
433
 
@@ -441,7 +436,7 @@ module ActiveRecord
441
436
  end
442
437
 
443
438
  private
444
- def get_records
439
+ def get_records(&block)
445
440
  return scope.to_a if skip_statement_cache?
446
441
 
447
442
  conn = klass.connection
@@ -453,13 +448,13 @@ module ActiveRecord
453
448
  end
454
449
 
455
450
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
456
- sc.execute binds, klass, klass.connection
451
+ sc.execute(binds, klass, klass.connection, &block)
457
452
  end
458
453
 
459
454
  def find_target
460
- records = get_records
461
- records.each { |record| set_inverse_instance(record) }
462
- records
455
+ get_records do |record|
456
+ set_inverse_instance(record)
457
+ end
463
458
  end
464
459
 
465
460
  # We have some records loaded from the database (persisted) and some that are
@@ -503,15 +498,19 @@ module ActiveRecord
503
498
  transaction do
504
499
  add_to_target(build_record(attributes)) do |record|
505
500
  yield(record) if block_given?
506
- insert_record(record, true, raise)
501
+ insert_record(record, true, raise) { @_was_loaded = loaded? }
507
502
  end
508
503
  end
509
504
  end
510
505
  end
511
506
 
512
507
  # Do the relevant stuff to insert the given record into the association collection.
513
- def insert_record(record, validate = true, raise = false)
514
- raise NotImplementedError
508
+ def insert_record(record, validate = true, raise = false, &block)
509
+ if raise
510
+ record.save!(validate: validate, &block)
511
+ else
512
+ record.save(validate: validate, &block)
513
+ end
515
514
  end
516
515
 
517
516
  def create_scope
@@ -521,7 +520,7 @@ module ActiveRecord
521
520
  def delete_or_destroy(records, method)
522
521
  records = records.flatten
523
522
  records.each { |record| raise_on_type_mismatch!(record) }
524
- existing_records = records.reject { |r| r.new_record? }
523
+ existing_records = records.reject(&:new_record?)
525
524
 
526
525
  if existing_records.empty?
527
526
  remove_records(existing_records, records, method)
@@ -565,19 +564,41 @@ module ActiveRecord
565
564
  end
566
565
  end
567
566
 
568
- def concat_records(records, should_raise = false)
567
+ def concat_records(records, raise = false)
569
568
  result = true
570
569
 
571
- records.flatten.each do |record|
570
+ records.each do |record|
572
571
  raise_on_type_mismatch!(record)
573
- add_to_target(record) do |rec|
574
- result &&= insert_record(rec, true, should_raise) unless owner.new_record?
572
+ add_to_target(record) do
573
+ result &&= insert_record(record, true, raise) { @_was_loaded = loaded? } unless owner.new_record?
575
574
  end
576
575
  end
577
576
 
578
577
  result && records
579
578
  end
580
579
 
580
+ def replace_on_target(record, index, skip_callbacks)
581
+ callback(:before_add, record) unless skip_callbacks
582
+
583
+ set_inverse_instance(record)
584
+
585
+ @_was_loaded = true
586
+
587
+ yield(record) if block_given?
588
+
589
+ if index
590
+ target[index] = record
591
+ elsif @_was_loaded || !loaded?
592
+ target << record
593
+ end
594
+
595
+ callback(:after_add, record) unless skip_callbacks
596
+
597
+ record
598
+ ensure
599
+ @_was_loaded = nil
600
+ end
601
+
581
602
  def callback(method, record)
582
603
  callbacks_for(method).each do |callback|
583
604
  callback.call(method, owner, record)
@@ -612,13 +633,8 @@ module ActiveRecord
612
633
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
613
634
  assoc = owner.association(reflection.through_reflection.name)
614
635
  assoc.reader.any? { |source|
615
- target_association = source.send(reflection.source_reflection.name)
616
-
617
- if target_association.respond_to?(:include?)
618
- target_association.include?(record)
619
- else
620
- target_association == record
621
- end
636
+ target_reflection = source.send(reflection.source_reflection.name)
637
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
622
638
  } || target.include?(record)
623
639
  else
624
640
  target.include?(record)
@@ -629,7 +645,7 @@ module ActiveRecord
629
645
  # specified, then #find scans the entire collection.
630
646
  def find_by_scan(*args)
631
647
  expects_array = args.first.kind_of?(Array)
632
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
648
+ ids = args.flatten.compact.map(&:to_s).uniq
633
649
 
634
650
  if ids.size == 1
635
651
  id = ids.first
@@ -28,13 +28,12 @@ module ActiveRecord
28
28
  # is computed directly through SQL and does not trigger by itself the
29
29
  # instantiation of the actual post records.
30
30
  class CollectionProxy < Relation
31
- delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
32
- delegate :find_nth, to: :scope
33
-
34
31
  def initialize(klass, association) #:nodoc:
35
32
  @association = association
36
- super klass, klass.arel_table
37
- merge! association.scope(nullify: false)
33
+ super klass, klass.arel_table, klass.predicate_builder
34
+
35
+ extensions = association.extensions
36
+ extend(*extensions) if extensions.any?
38
37
  end
39
38
 
40
39
  def target
@@ -112,7 +111,7 @@ module ActiveRecord
112
111
  end
113
112
 
114
113
  # Finds an object in the collection responding to the +id+. Uses the same
115
- # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
114
+ # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
116
115
  # error if the object cannot be found.
117
116
  #
118
117
  # class Person < ActiveRecord::Base
@@ -127,7 +126,7 @@ module ActiveRecord
127
126
  # # ]
128
127
  #
129
128
  # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
130
- # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
129
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
131
130
  #
132
131
  # person.pets.find(2) { |pet| pet.name.downcase! }
133
132
  # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
@@ -171,32 +170,42 @@ module ActiveRecord
171
170
  @association.first(*args)
172
171
  end
173
172
 
174
- # Same as +first+ except returns only the second record.
173
+ # Same as #first except returns only the second record.
175
174
  def second(*args)
176
175
  @association.second(*args)
177
176
  end
178
177
 
179
- # Same as +first+ except returns only the third record.
178
+ # Same as #first except returns only the third record.
180
179
  def third(*args)
181
180
  @association.third(*args)
182
181
  end
183
182
 
184
- # Same as +first+ except returns only the fourth record.
183
+ # Same as #first except returns only the fourth record.
185
184
  def fourth(*args)
186
185
  @association.fourth(*args)
187
186
  end
188
187
 
189
- # Same as +first+ except returns only the fifth record.
188
+ # Same as #first except returns only the fifth record.
190
189
  def fifth(*args)
191
190
  @association.fifth(*args)
192
191
  end
193
192
 
194
- # Same as +first+ except returns only the forty second record.
193
+ # Same as #first except returns only the forty second record.
195
194
  # Also known as accessing "the reddit".
196
195
  def forty_two(*args)
197
196
  @association.forty_two(*args)
198
197
  end
199
198
 
199
+ # Same as #first except returns only the third-to-last record.
200
+ def third_to_last(*args)
201
+ @association.third_to_last(*args)
202
+ end
203
+
204
+ # Same as #first except returns only the second-to-last record.
205
+ def second_to_last(*args)
206
+ @association.second_to_last(*args)
207
+ end
208
+
200
209
  # Returns the last record, or the last +n+ records, from the collection.
201
210
  # If the collection is empty, the first form returns +nil+, and the second
202
211
  # form returns an empty array.
@@ -227,6 +236,31 @@ module ActiveRecord
227
236
  @association.last(*args)
228
237
  end
229
238
 
239
+ # Gives a record (or N records if a parameter is supplied) from the collection
240
+ # using the same rules as <tt>ActiveRecord::Base.take</tt>.
241
+ #
242
+ # class Person < ActiveRecord::Base
243
+ # has_many :pets
244
+ # end
245
+ #
246
+ # person.pets
247
+ # # => [
248
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
249
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
250
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
251
+ # # ]
252
+ #
253
+ # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
254
+ #
255
+ # person.pets.take(2)
256
+ # # => [
257
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
258
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
259
+ # # ]
260
+ #
261
+ # another_person_without.pets # => []
262
+ # another_person_without.pets.take # => nil
263
+ # another_person_without.pets.take(2) # => []
230
264
  def take(n = nil)
231
265
  @association.take(n)
232
266
  end
@@ -290,7 +324,7 @@ module ActiveRecord
290
324
  @association.create(attributes, &block)
291
325
  end
292
326
 
293
- # Like +create+, except that if the record is invalid, raises an exception.
327
+ # Like #create, except that if the record is invalid, raises an exception.
294
328
  #
295
329
  # class Person
296
330
  # has_many :pets
@@ -307,8 +341,8 @@ module ActiveRecord
307
341
  end
308
342
 
309
343
  # Add one or more records to the collection by setting their foreign keys
310
- # to the association's primary key. Since << flattens its argument list and
311
- # inserts each record, +push+ and +concat+ behave identically. Returns +self+
344
+ # to the association's primary key. Since #<< flattens its argument list and
345
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
312
346
  # so method calls may be chained.
313
347
  #
314
348
  # class Person < ActiveRecord::Base
@@ -364,7 +398,7 @@ module ActiveRecord
364
398
  # specified by the +:dependent+ option. If no +:dependent+ option is given,
365
399
  # then it will follow the default strategy.
366
400
  #
367
- # For +has_many :through+ associations, the default deletion strategy is
401
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
368
402
  # +:delete_all+.
369
403
  #
370
404
  # For +has_many+ associations, the default deletion strategy is +:nullify+.
@@ -399,7 +433,7 @@ module ActiveRecord
399
433
  # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
400
434
  # # ]
401
435
  #
402
- # Both +has_many+ and +has_many :through+ dependencies default to the
436
+ # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
403
437
  # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
404
438
  # Records are not instantiated and callbacks will not be fired.
405
439
  #
@@ -418,7 +452,7 @@ module ActiveRecord
418
452
  # person.pets.delete_all
419
453
  #
420
454
  # Pet.find(1, 2, 3)
421
- # # => ActiveRecord::RecordNotFound
455
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
422
456
  #
423
457
  # If it is set to <tt>:delete_all</tt>, all the objects are deleted
424
458
  # *without* calling their +destroy+ method.
@@ -438,7 +472,7 @@ module ActiveRecord
438
472
  # person.pets.delete_all
439
473
  #
440
474
  # Pet.find(1, 2, 3)
441
- # # => ActiveRecord::RecordNotFound
475
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
442
476
  def delete_all(dependent = nil)
443
477
  @association.delete_all(dependent)
444
478
  end
@@ -475,7 +509,7 @@ module ActiveRecord
475
509
  # then it will follow the default strategy. Returns an array with the
476
510
  # deleted records.
477
511
  #
478
- # For +has_many :through+ associations, the default deletion strategy is
512
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
479
513
  # +:delete_all+.
480
514
  #
481
515
  # For +has_many+ associations, the default deletion strategy is +:nullify+.
@@ -532,7 +566,7 @@ module ActiveRecord
532
566
  # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
533
567
  #
534
568
  # Pet.find(1, 3)
535
- # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
569
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
536
570
  #
537
571
  # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
538
572
  # *without* calling their +destroy+ method.
@@ -560,7 +594,7 @@ module ActiveRecord
560
594
  # # ]
561
595
  #
562
596
  # Pet.find(1)
563
- # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
597
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
564
598
  #
565
599
  # You can pass +Integer+ or +String+ values, it finds the records
566
600
  # responding to the +id+ and executes delete on them.
@@ -624,7 +658,7 @@ module ActiveRecord
624
658
  # person.pets.size # => 0
625
659
  # person.pets # => []
626
660
  #
627
- # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
661
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
628
662
  #
629
663
  # You can pass +Integer+ or +String+ values, it finds the records
630
664
  # responding to the +id+ and then deletes them from the database.
@@ -656,7 +690,7 @@ module ActiveRecord
656
690
  # person.pets.size # => 0
657
691
  # person.pets # => []
658
692
  #
659
- # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
693
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
660
694
  def destroy(*records)
661
695
  @association.destroy(*records)
662
696
  end
@@ -693,10 +727,16 @@ module ActiveRecord
693
727
  # # #<Pet id: 2, name: "Spook", person_id: 1>,
694
728
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
695
729
  # # ]
696
- def count(column_name = nil, options = {})
697
- # TODO: Remove options argument as soon we remove support to
698
- # activerecord-deprecated_finders.
699
- @association.count(column_name, options)
730
+ def count(column_name = nil)
731
+ @association.count(column_name)
732
+ end
733
+
734
+ def calculate(operation, column_name)
735
+ null_scope? ? scope.calculate(operation, column_name) : super
736
+ end
737
+
738
+ def pluck(*column_names)
739
+ null_scope? ? scope.pluck(*column_names) : super
700
740
  end
701
741
 
702
742
  # Returns the size of the collection. If the collection hasn't been loaded,
@@ -754,7 +794,7 @@ module ActiveRecord
754
794
  # Returns +true+ if the collection is empty. If the collection has been
755
795
  # loaded it is equivalent
756
796
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
757
- # it is equivalent to <tt>collection.exists?</tt>. If the collection has
797
+ # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
758
798
  # not already been loaded and you are going to fetch the records anyway it
759
799
  # is better to check <tt>collection.length.zero?</tt>.
760
800
  #
@@ -783,7 +823,7 @@ module ActiveRecord
783
823
  # person.pets.any? # => false
784
824
  #
785
825
  # person.pets << Pet.new(name: 'Snoop')
786
- # person.pets.count # => 0
826
+ # person.pets.count # => 1
787
827
  # person.pets.any? # => true
788
828
  #
789
829
  # You can also pass a +block+ to define criteria. The behavior
@@ -858,27 +898,14 @@ module ActiveRecord
858
898
  !!@association.include?(record)
859
899
  end
860
900
 
861
- def arel
862
- scope.arel
863
- end
864
-
865
901
  def proxy_association
866
902
  @association
867
903
  end
868
904
 
869
- # We don't want this object to be put on the scoping stack, because
870
- # that could create an infinite loop where we call an @association
871
- # method, which gets the current scope, which is this object, which
872
- # delegates to @association, and so on.
873
- def scoping
874
- @association.scope.scoping { yield }
875
- end
876
-
877
905
  # Returns a <tt>Relation</tt> object for the records in this association
878
906
  def scope
879
- @association.scope
907
+ @scope ||= @association.scope
880
908
  end
881
- alias spawn scope
882
909
 
883
910
  # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
884
911
  # contain the same number of elements and if each element is equal
@@ -946,6 +973,10 @@ module ActiveRecord
946
973
  end
947
974
  alias_method :to_a, :to_ary
948
975
 
976
+ def records # :nodoc:
977
+ load_target
978
+ end
979
+
949
980
  # Adds one or more +records+ to the collection by setting their foreign keys
950
981
  # to the association's primary key. Returns +self+, so several appends may be
951
982
  # chained together.
@@ -973,12 +1004,15 @@ module ActiveRecord
973
1004
  alias_method :append, :<<
974
1005
 
975
1006
  def prepend(*args)
976
- raise NoMethodError, "prepend on association is not defined. Please use << or append"
1007
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
977
1008
  end
978
1009
 
979
1010
  # Equivalent to +delete_all+. The difference is that returns +self+, instead
980
1011
  # of an array with the deleted objects, so methods can be chained. See
981
1012
  # +delete_all+ for more information.
1013
+ # Note that because +delete_all+ removes records by directly
1014
+ # running an SQL query into the database, the +updated_at+ column of
1015
+ # the object is not changed.
982
1016
  def clear
983
1017
  delete_all
984
1018
  self
@@ -1004,7 +1038,7 @@ module ActiveRecord
1004
1038
  # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1005
1039
  def reload
1006
1040
  proxy_association.reload
1007
- self
1041
+ reset_scope
1008
1042
  end
1009
1043
 
1010
1044
  # Unloads the association. Returns +self+.
@@ -1026,8 +1060,32 @@ module ActiveRecord
1026
1060
  def reset
1027
1061
  proxy_association.reset
1028
1062
  proxy_association.reset_scope
1063
+ reset_scope
1064
+ end
1065
+
1066
+ def reset_scope # :nodoc:
1067
+ @scope = nil
1029
1068
  self
1030
1069
  end
1070
+
1071
+ delegate_methods = [
1072
+ QueryMethods,
1073
+ SpawnMethods,
1074
+ ].flat_map { |klass|
1075
+ klass.public_instance_methods(false)
1076
+ } - self.public_instance_methods(false) + [:scoping]
1077
+
1078
+ delegate(*delegate_methods, to: :scope)
1079
+
1080
+ private
1081
+
1082
+ def null_scope?
1083
+ @association.null_scope?
1084
+ end
1085
+
1086
+ def exec_queries
1087
+ load_target
1088
+ end
1031
1089
  end
1032
1090
  end
1033
1091
  end