activerecord 4.2.0 → 5.0.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1537 -789
  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/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +16 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +23 -9
  11. data/lib/active_record/associations/association_scope.rb +74 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +26 -29
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +12 -20
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +61 -33
  21. data/lib/active_record/associations/collection_proxy.rb +81 -35
  22. data/lib/active_record/associations/foreign_association.rb +11 -0
  23. data/lib/active_record/associations/has_many_association.rb +21 -57
  24. data/lib/active_record/associations/has_many_through_association.rb +15 -45
  25. data/lib/active_record/associations/has_one_association.rb +13 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +37 -21
  28. data/lib/active_record/associations/preloader/association.rb +51 -53
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +18 -8
  34. data/lib/active_record/associations/singular_association.rb +8 -8
  35. data/lib/active_record/associations/through_association.rb +22 -9
  36. data/lib/active_record/associations.rb +321 -212
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +79 -15
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +51 -81
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +37 -15
  52. data/lib/active_record/attribute_set.rb +34 -3
  53. data/lib/active_record/attributes.rb +199 -73
  54. data/lib/active_record/autosave_association.rb +73 -25
  55. data/lib/active_record/base.rb +35 -27
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  101. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  102. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +38 -15
  121. data/lib/active_record/core.rb +109 -114
  122. data/lib/active_record/counter_cache.rb +14 -25
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +115 -79
  125. data/lib/active_record/errors.rb +88 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +2 -2
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +84 -46
  130. data/lib/active_record/gem_version.rb +2 -2
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +27 -25
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration/command_recorder.rb +59 -18
  140. data/lib/active_record/migration/compatibility.rb +126 -0
  141. data/lib/active_record/migration.rb +372 -114
  142. data/lib/active_record/model_schema.rb +128 -38
  143. data/lib/active_record/nested_attributes.rb +71 -32
  144. data/lib/active_record/no_touching.rb +1 -1
  145. data/lib/active_record/null_relation.rb +16 -8
  146. data/lib/active_record/persistence.rb +124 -80
  147. data/lib/active_record/query_cache.rb +15 -18
  148. data/lib/active_record/querying.rb +10 -9
  149. data/lib/active_record/railtie.rb +28 -19
  150. data/lib/active_record/railties/controller_runtime.rb +1 -1
  151. data/lib/active_record/railties/databases.rake +67 -51
  152. data/lib/active_record/readonly_attributes.rb +1 -1
  153. data/lib/active_record/reflection.rb +318 -139
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  155. data/lib/active_record/relation/batches.rb +139 -34
  156. data/lib/active_record/relation/calculations.rb +80 -102
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +167 -97
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +38 -41
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  167. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  169. data/lib/active_record/relation/predicate_builder.rb +124 -82
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +323 -257
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +11 -10
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/relation.rb +176 -115
  177. data/lib/active_record/result.rb +4 -3
  178. data/lib/active_record/runtime_registry.rb +1 -1
  179. data/lib/active_record/sanitization.rb +95 -66
  180. data/lib/active_record/schema.rb +26 -22
  181. data/lib/active_record/schema_dumper.rb +62 -38
  182. data/lib/active_record/schema_migration.rb +11 -17
  183. data/lib/active_record/scoping/default.rb +24 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/scoping.rb +32 -15
  186. data/lib/active_record/secure_token.rb +38 -0
  187. data/lib/active_record/serialization.rb +2 -4
  188. data/lib/active_record/statement_cache.rb +16 -14
  189. data/lib/active_record/store.rb +8 -3
  190. data/lib/active_record/suppressor.rb +58 -0
  191. data/lib/active_record/table_metadata.rb +68 -0
  192. data/lib/active_record/tasks/database_tasks.rb +59 -42
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  196. data/lib/active_record/timestamp.rb +20 -9
  197. data/lib/active_record/touch_later.rb +58 -0
  198. data/lib/active_record/transactions.rb +159 -67
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -41
  201. data/lib/active_record/type/date_time.rb +2 -38
  202. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  203. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  204. data/lib/active_record/type/internal/timezone.rb +15 -0
  205. data/lib/active_record/type/serialized.rb +21 -14
  206. data/lib/active_record/type/time.rb +10 -16
  207. data/lib/active_record/type/type_map.rb +4 -4
  208. data/lib/active_record/type.rb +66 -17
  209. data/lib/active_record/type_caster/connection.rb +29 -0
  210. data/lib/active_record/type_caster/map.rb +19 -0
  211. data/lib/active_record/type_caster.rb +7 -0
  212. data/lib/active_record/validations/absence.rb +23 -0
  213. data/lib/active_record/validations/associated.rb +10 -3
  214. data/lib/active_record/validations/length.rb +24 -0
  215. data/lib/active_record/validations/presence.rb +11 -12
  216. data/lib/active_record/validations/uniqueness.rb +29 -18
  217. data/lib/active_record/validations.rb +33 -32
  218. data/lib/active_record.rb +9 -2
  219. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  220. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
  221. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
  222. data/lib/rails/generators/active_record/migration.rb +7 -0
  223. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  224. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  225. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  226. metadata +60 -34
  227. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  228. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  229. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  231. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  232. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  233. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  234. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  235. data/lib/active_record/type/big_integer.rb +0 -13
  236. data/lib/active_record/type/binary.rb +0 -50
  237. data/lib/active_record/type/boolean.rb +0 -30
  238. data/lib/active_record/type/decimal.rb +0 -40
  239. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  240. data/lib/active_record/type/decorator.rb +0 -14
  241. data/lib/active_record/type/float.rb +0 -19
  242. data/lib/active_record/type/integer.rb +0 -55
  243. data/lib/active_record/type/mutable.rb +0 -16
  244. data/lib/active_record/type/numeric.rb +0 -36
  245. data/lib/active_record/type/string.rb +0 -36
  246. data/lib/active_record/type/text.rb +0 -11
  247. data/lib/active_record/type/time_value.rb +0 -38
  248. data/lib/active_record/type/unsigned_integer.rb +0 -15
  249. data/lib/active_record/type/value.rb +0 -101
@@ -28,15 +28,21 @@ 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?
42
+ if null_scope?
37
43
  # Cache the proxy separately before the owner has an id
38
44
  # or else a post-save proxy will still lack the id
39
- @new_record_proxy ||= CollectionProxy.create(klass, self)
45
+ @null_proxy ||= CollectionProxy.create(klass, self)
40
46
  else
41
47
  @proxy ||= CollectionProxy.create(klass, self)
42
48
  end
@@ -54,17 +60,22 @@ module ActiveRecord
54
60
  record.send(reflection.association_primary_key)
55
61
  end
56
62
  else
57
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
58
- scope.pluck(column)
63
+ @association_ids ||= (
64
+ column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
65
+ scope.pluck(column)
66
+ )
59
67
  end
60
68
  end
61
69
 
62
70
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
71
  def ids_writer(ids)
64
72
  pk_type = reflection.primary_key_type
65
- ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_type.type_cast_from_user(i) }
67
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
73
+ ids = Array(ids).reject(&:blank?)
74
+ ids.map! { |i| pk_type.cast(i) }
75
+ records = klass.where(reflection.association_primary_key => ids).index_by do |r|
76
+ r.send(reflection.association_primary_key)
77
+ end.values_at(*ids)
78
+ replace(records)
68
79
  end
69
80
 
70
81
  def reset
@@ -125,10 +136,28 @@ module ActiveRecord
125
136
  first_nth_or_last(:forty_two, *args)
126
137
  end
127
138
 
139
+ def third_to_last(*args)
140
+ first_nth_or_last(:third_to_last, *args)
141
+ end
142
+
143
+ def second_to_last(*args)
144
+ first_nth_or_last(:second_to_last, *args)
145
+ end
146
+
128
147
  def last(*args)
129
148
  first_nth_or_last(:last, *args)
130
149
  end
131
150
 
151
+ def take(n = nil)
152
+ if loaded?
153
+ n ? target.take(n) : target.first
154
+ else
155
+ scope.take(n).tap do |record|
156
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
157
+ end
158
+ end
159
+ end
160
+
132
161
  def build(attributes = {}, &block)
133
162
  if attributes.is_a?(Array)
134
163
  attributes.collect { |attr| build(attr, &block) }
@@ -151,6 +180,7 @@ module ActiveRecord
151
180
  # be chained. Since << flattens its argument list and inserts each record,
152
181
  # +push+ and +concat+ behave identically.
153
182
  def concat(*records)
183
+ records = records.flatten
154
184
  if owner.new_record?
155
185
  load_target
156
186
  concat_records(records)
@@ -218,11 +248,7 @@ module ActiveRecord
218
248
 
219
249
  # Count all records using SQL. Construct options and pass them with
220
250
  # scope to the target class's +count+.
221
- def count(column_name = nil, count_options = {})
222
- # TODO: Remove count_options argument as soon we remove support to
223
- # activerecord-deprecated_finders.
224
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
225
-
251
+ def count(column_name = nil)
226
252
  relation = scope
227
253
  if association_scope.distinct_value
228
254
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -254,7 +280,7 @@ module ActiveRecord
254
280
  _options = records.extract_options!
255
281
  dependent = _options[:dependent] || options[:dependent]
256
282
 
257
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
283
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
258
284
  delete_or_destroy(records, dependent)
259
285
  end
260
286
 
@@ -265,7 +291,7 @@ module ActiveRecord
265
291
  # +:dependent+ option.
266
292
  def destroy(*records)
267
293
  return if records.empty?
268
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
294
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
269
295
  delete_or_destroy(records, :destroy)
270
296
  end
271
297
 
@@ -289,7 +315,7 @@ module ActiveRecord
289
315
  elsif !loaded? && !association_scope.group_values.empty?
290
316
  load_target.size
291
317
  elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
292
- unsaved_records = target.select { |r| r.new_record? }
318
+ unsaved_records = target.select(&:new_record?)
293
319
  unsaved_records.size + count_records
294
320
  else
295
321
  count_records
@@ -322,7 +348,8 @@ module ActiveRecord
322
348
  end
323
349
 
324
350
  # Returns true if the collections is not empty.
325
- # Equivalent to +!collection.empty?+.
351
+ # If block given, loads all records and checks for one or more matches.
352
+ # Otherwise, equivalent to +!collection.empty?+.
326
353
  def any?
327
354
  if block_given?
328
355
  load_target.any? { |*block_args| yield(*block_args) }
@@ -332,7 +359,8 @@ module ActiveRecord
332
359
  end
333
360
 
334
361
  # Returns true if the collection has more than 1 record.
335
- # Equivalent to +collection.size > 1+.
362
+ # If block given, loads all records and checks for two or more matches.
363
+ # Otherwise, equivalent to +collection.size > 1+.
336
364
  def many?
337
365
  if block_given?
338
366
  load_target.many? { |*block_args| yield(*block_args) }
@@ -361,6 +389,8 @@ module ActiveRecord
361
389
  replace_common_records_in_memory(other_array, original_target)
362
390
  if other_array != original_target
363
391
  transaction { replace_records(other_array, original_target) }
392
+ else
393
+ other_array
364
394
  end
365
395
  end
366
396
  end
@@ -395,12 +425,16 @@ module ActiveRecord
395
425
 
396
426
  def replace_on_target(record, index, skip_callbacks)
397
427
  callback(:before_add, record) unless skip_callbacks
428
+
429
+ was_loaded = loaded?
398
430
  yield(record) if block_given?
399
431
 
400
- if index
401
- @target[index] = record
402
- else
403
- @target << record
432
+ unless !was_loaded && loaded?
433
+ if index
434
+ @target[index] = record
435
+ else
436
+ @target << record
437
+ end
404
438
  end
405
439
 
406
440
  callback(:after_add, record) unless skip_callbacks
@@ -421,13 +455,7 @@ module ActiveRecord
421
455
 
422
456
  private
423
457
  def get_records
424
- if reflection.scope_chain.any?(&:any?) ||
425
- scope.eager_loading? ||
426
- klass.current_scope ||
427
- klass.default_scopes.any?
428
-
429
- return scope.to_a
430
- end
458
+ return scope.to_a if skip_statement_cache?
431
459
 
432
460
  conn = klass.connection
433
461
  sc = reflection.association_scope_cache(conn, owner) do
@@ -506,7 +534,7 @@ module ActiveRecord
506
534
  def delete_or_destroy(records, method)
507
535
  records = records.flatten
508
536
  records.each { |record| raise_on_type_mismatch!(record) }
509
- existing_records = records.reject { |r| r.new_record? }
537
+ existing_records = records.reject(&:new_record?)
510
538
 
511
539
  if existing_records.empty?
512
540
  remove_records(existing_records, records, method)
@@ -553,7 +581,7 @@ module ActiveRecord
553
581
  def concat_records(records, should_raise = false)
554
582
  result = true
555
583
 
556
- records.flatten.each do |record|
584
+ records.each do |record|
557
585
  raise_on_type_mismatch!(record)
558
586
  add_to_target(record) do |rec|
559
587
  result &&= insert_record(rec, true, should_raise) unless owner.new_record?
@@ -597,8 +625,8 @@ module ActiveRecord
597
625
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
598
626
  assoc = owner.association(reflection.through_reflection.name)
599
627
  assoc.reader.any? { |source|
600
- target = source.send(reflection.source_reflection.name)
601
- target.respond_to?(:include?) ? target.include?(record) : target == record
628
+ target_reflection = source.send(reflection.source_reflection.name)
629
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
602
630
  } || target.include?(record)
603
631
  else
604
632
  target.include?(record)
@@ -609,7 +637,7 @@ module ActiveRecord
609
637
  # specified, then #find scans the entire collection.
610
638
  def find_by_scan(*args)
611
639
  expects_array = args.first.kind_of?(Array)
612
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
640
+ ids = args.flatten.compact.map(&:to_s).uniq
613
641
 
614
642
  if ids.size == 1
615
643
  id = ids.first
@@ -29,10 +29,11 @@ module ActiveRecord
29
29
  # instantiation of the actual post records.
30
30
  class CollectionProxy < Relation
31
31
  delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
32
+ delegate :find_nth, to: :scope
32
33
 
33
34
  def initialize(klass, association) #:nodoc:
34
35
  @association = association
35
- super klass, klass.arel_table
36
+ super klass, klass.arel_table, klass.predicate_builder
36
37
  merge! association.scope(nullify: false)
37
38
  end
38
39
 
@@ -111,7 +112,7 @@ module ActiveRecord
111
112
  end
112
113
 
113
114
  # Finds an object in the collection responding to the +id+. Uses the same
114
- # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
115
+ # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
115
116
  # error if the object cannot be found.
116
117
  #
117
118
  # class Person < ActiveRecord::Base
@@ -126,7 +127,7 @@ module ActiveRecord
126
127
  # # ]
127
128
  #
128
129
  # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
129
- # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
130
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
130
131
  #
131
132
  # person.pets.find(2) { |pet| pet.name.downcase! }
132
133
  # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
@@ -170,32 +171,42 @@ module ActiveRecord
170
171
  @association.first(*args)
171
172
  end
172
173
 
173
- # Same as +first+ except returns only the second record.
174
+ # Same as #first except returns only the second record.
174
175
  def second(*args)
175
176
  @association.second(*args)
176
177
  end
177
178
 
178
- # Same as +first+ except returns only the third record.
179
+ # Same as #first except returns only the third record.
179
180
  def third(*args)
180
181
  @association.third(*args)
181
182
  end
182
183
 
183
- # Same as +first+ except returns only the fourth record.
184
+ # Same as #first except returns only the fourth record.
184
185
  def fourth(*args)
185
186
  @association.fourth(*args)
186
187
  end
187
188
 
188
- # Same as +first+ except returns only the fifth record.
189
+ # Same as #first except returns only the fifth record.
189
190
  def fifth(*args)
190
191
  @association.fifth(*args)
191
192
  end
192
193
 
193
- # Same as +first+ except returns only the forty second record.
194
+ # Same as #first except returns only the forty second record.
194
195
  # Also known as accessing "the reddit".
195
196
  def forty_two(*args)
196
197
  @association.forty_two(*args)
197
198
  end
198
199
 
200
+ # Same as #first except returns only the third-to-last record.
201
+ def third_to_last(*args)
202
+ @association.third_to_last(*args)
203
+ end
204
+
205
+ # Same as #first except returns only the second-to-last record.
206
+ def second_to_last(*args)
207
+ @association.second_to_last(*args)
208
+ end
209
+
199
210
  # Returns the last record, or the last +n+ records, from the collection.
200
211
  # If the collection is empty, the first form returns +nil+, and the second
201
212
  # form returns an empty array.
@@ -226,6 +237,35 @@ module ActiveRecord
226
237
  @association.last(*args)
227
238
  end
228
239
 
240
+ # Gives a record (or N records if a parameter is supplied) from the collection
241
+ # using the same rules as <tt>ActiveRecord::Base.take</tt>.
242
+ #
243
+ # class Person < ActiveRecord::Base
244
+ # has_many :pets
245
+ # end
246
+ #
247
+ # person.pets
248
+ # # => [
249
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
250
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
251
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
252
+ # # ]
253
+ #
254
+ # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
255
+ #
256
+ # person.pets.take(2)
257
+ # # => [
258
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
259
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
260
+ # # ]
261
+ #
262
+ # another_person_without.pets # => []
263
+ # another_person_without.pets.take # => nil
264
+ # another_person_without.pets.take(2) # => []
265
+ def take(n = nil)
266
+ @association.take(n)
267
+ end
268
+
229
269
  # Returns a new object of the collection type that has been instantiated
230
270
  # with +attributes+ and linked to this object, but have not yet been saved.
231
271
  # You can pass an array of attributes hashes, this will return an array
@@ -285,7 +325,7 @@ module ActiveRecord
285
325
  @association.create(attributes, &block)
286
326
  end
287
327
 
288
- # Like +create+, except that if the record is invalid, raises an exception.
328
+ # Like #create, except that if the record is invalid, raises an exception.
289
329
  #
290
330
  # class Person
291
331
  # has_many :pets
@@ -302,8 +342,8 @@ module ActiveRecord
302
342
  end
303
343
 
304
344
  # Add one or more records to the collection by setting their foreign keys
305
- # to the association's primary key. Since << flattens its argument list and
306
- # inserts each record, +push+ and +concat+ behave identically. Returns +self+
345
+ # to the association's primary key. Since #<< flattens its argument list and
346
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
307
347
  # so method calls may be chained.
308
348
  #
309
349
  # class Person < ActiveRecord::Base
@@ -359,7 +399,7 @@ module ActiveRecord
359
399
  # specified by the +:dependent+ option. If no +:dependent+ option is given,
360
400
  # then it will follow the default strategy.
361
401
  #
362
- # For +has_many :through+ associations, the default deletion strategy is
402
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
363
403
  # +:delete_all+.
364
404
  #
365
405
  # For +has_many+ associations, the default deletion strategy is +:nullify+.
@@ -394,7 +434,7 @@ module ActiveRecord
394
434
  # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
395
435
  # # ]
396
436
  #
397
- # Both +has_many+ and +has_many :through+ dependencies default to the
437
+ # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
398
438
  # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
399
439
  # Records are not instantiated and callbacks will not be fired.
400
440
  #
@@ -413,7 +453,7 @@ module ActiveRecord
413
453
  # person.pets.delete_all
414
454
  #
415
455
  # Pet.find(1, 2, 3)
416
- # # => ActiveRecord::RecordNotFound
456
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
417
457
  #
418
458
  # If it is set to <tt>:delete_all</tt>, all the objects are deleted
419
459
  # *without* calling their +destroy+ method.
@@ -433,7 +473,7 @@ module ActiveRecord
433
473
  # person.pets.delete_all
434
474
  #
435
475
  # Pet.find(1, 2, 3)
436
- # # => ActiveRecord::RecordNotFound
476
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
437
477
  def delete_all(dependent = nil)
438
478
  @association.delete_all(dependent)
439
479
  end
@@ -465,15 +505,16 @@ module ActiveRecord
465
505
  @association.destroy_all
466
506
  end
467
507
 
468
- # Deletes the +records+ supplied and removes them from the collection. For
469
- # +has_many+ associations, the deletion is done according to the strategy
470
- # specified by the <tt>:dependent</tt> option. Returns an array with the
508
+ # Deletes the +records+ supplied from the collection according to the strategy
509
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
510
+ # then it will follow the default strategy. Returns an array with the
471
511
  # deleted records.
472
512
  #
473
- # If no <tt>:dependent</tt> option is given, then it will follow the default
474
- # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
475
- # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
476
- # strategy is +delete_all+.
513
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
514
+ # +:delete_all+.
515
+ #
516
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
517
+ # This sets the foreign keys to +NULL+.
477
518
  #
478
519
  # class Person < ActiveRecord::Base
479
520
  # has_many :pets # dependent: :nullify option by default
@@ -526,7 +567,7 @@ module ActiveRecord
526
567
  # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
527
568
  #
528
569
  # Pet.find(1, 3)
529
- # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
570
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
530
571
  #
531
572
  # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
532
573
  # *without* calling their +destroy+ method.
@@ -554,9 +595,9 @@ module ActiveRecord
554
595
  # # ]
555
596
  #
556
597
  # Pet.find(1)
557
- # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
598
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
558
599
  #
559
- # You can pass +Fixnum+ or +String+ values, it finds the records
600
+ # You can pass +Integer+ or +String+ values, it finds the records
560
601
  # responding to the +id+ and executes delete on them.
561
602
  #
562
603
  # class Person < ActiveRecord::Base
@@ -618,9 +659,9 @@ module ActiveRecord
618
659
  # person.pets.size # => 0
619
660
  # person.pets # => []
620
661
  #
621
- # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
662
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
622
663
  #
623
- # You can pass +Fixnum+ or +String+ values, it finds the records
664
+ # You can pass +Integer+ or +String+ values, it finds the records
624
665
  # responding to the +id+ and then deletes them from the database.
625
666
  #
626
667
  # person.pets.size # => 3
@@ -650,7 +691,7 @@ module ActiveRecord
650
691
  # person.pets.size # => 0
651
692
  # person.pets # => []
652
693
  #
653
- # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
694
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
654
695
  def destroy(*records)
655
696
  @association.destroy(*records)
656
697
  end
@@ -687,10 +728,8 @@ module ActiveRecord
687
728
  # # #<Pet id: 2, name: "Spook", person_id: 1>,
688
729
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
689
730
  # # ]
690
- def count(column_name = nil, options = {})
691
- # TODO: Remove options argument as soon we remove support to
692
- # activerecord-deprecated_finders.
693
- @association.count(column_name, options)
731
+ def count(column_name = nil)
732
+ @association.count(column_name)
694
733
  end
695
734
 
696
735
  # Returns the size of the collection. If the collection hasn't been loaded,
@@ -777,7 +816,7 @@ module ActiveRecord
777
816
  # person.pets.any? # => false
778
817
  #
779
818
  # person.pets << Pet.new(name: 'Snoop')
780
- # person.pets.count # => 0
819
+ # person.pets.count # => 1
781
820
  # person.pets.any? # => true
782
821
  #
783
822
  # You can also pass a +block+ to define criteria. The behavior
@@ -852,7 +891,7 @@ module ActiveRecord
852
891
  !!@association.include?(record)
853
892
  end
854
893
 
855
- def arel
894
+ def arel #:nodoc:
856
895
  scope.arel
857
896
  end
858
897
 
@@ -940,6 +979,10 @@ module ActiveRecord
940
979
  end
941
980
  alias_method :to_a, :to_ary
942
981
 
982
+ def records # :nodoc:
983
+ load_target
984
+ end
985
+
943
986
  # Adds one or more +records+ to the collection by setting their foreign keys
944
987
  # to the association's primary key. Returns +self+, so several appends may be
945
988
  # chained together.
@@ -967,12 +1010,15 @@ module ActiveRecord
967
1010
  alias_method :append, :<<
968
1011
 
969
1012
  def prepend(*args)
970
- raise NoMethodError, "prepend on association is not defined. Please use << or append"
1013
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
971
1014
  end
972
1015
 
973
1016
  # Equivalent to +delete_all+. The difference is that returns +self+, instead
974
1017
  # of an array with the deleted objects, so methods can be chained. See
975
1018
  # +delete_all+ for more information.
1019
+ # Note that because +delete_all+ removes records by directly
1020
+ # running an SQL query into the database, the +updated_at+ column of
1021
+ # the object is not changed.
976
1022
  def clear
977
1023
  delete_all
978
1024
  self
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation # :nodoc:
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  # If the association has a <tt>:through</tt> option further specialization
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
+ include ForeignAssociation
9
10
 
10
11
  def handle_dependency
11
12
  case options[:dependent]
@@ -14,9 +15,16 @@ module ActiveRecord
14
15
 
15
16
  when :restrict_with_error
16
17
  unless empty?
17
- record = klass.human_attribute_name(reflection.name).downcase
18
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
19
- false
18
+ record = owner.class.human_attribute_name(reflection.name).downcase
19
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
20
+ if message
21
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
22
+ The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
23
+ Please use `:'restrict_dependent_destroy.has_many'` instead.
24
+ MESSAGE
25
+ end
26
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
27
+ throw(:abort)
20
28
  end
21
29
 
22
30
  else
@@ -42,7 +50,7 @@ module ActiveRecord
42
50
  end
43
51
 
44
52
  def empty?
45
- if has_cached_counter?
53
+ if reflection.has_cached_counter?
46
54
  size.zero?
47
55
  else
48
56
  super
@@ -65,8 +73,8 @@ module ActiveRecord
65
73
  # If the collection is empty the target is set to an empty array and
66
74
  # the loaded flag is set to true as well.
67
75
  def count_records
68
- count = if has_cached_counter?
69
- owner._read_attribute cached_counter_attribute_name
76
+ count = if reflection.has_cached_counter?
77
+ owner._read_attribute reflection.counter_cache_column
70
78
  else
71
79
  scope.count
72
80
  end
@@ -79,56 +87,20 @@ module ActiveRecord
79
87
  [association_scope.limit_value, count].compact.min
80
88
  end
81
89
 
82
- def has_cached_counter?(reflection = reflection())
83
- owner.attribute_present?(cached_counter_attribute_name(reflection))
84
- end
85
-
86
- def cached_counter_attribute_name(reflection = reflection())
87
- options[:counter_cache] || "#{reflection.name}_count"
88
- end
89
-
90
90
  def update_counter(difference, reflection = reflection())
91
- update_counter_in_database(difference, reflection)
92
- update_counter_in_memory(difference, reflection)
93
- end
94
-
95
- def update_counter_in_database(difference, reflection = reflection())
96
- if has_cached_counter?(reflection)
97
- counter = cached_counter_attribute_name(reflection)
98
- owner.class.update_counters(owner.id, counter => difference)
91
+ if reflection.has_cached_counter?
92
+ owner.increment!(reflection.counter_cache_column, difference)
99
93
  end
100
94
  end
101
95
 
102
96
  def update_counter_in_memory(difference, reflection = reflection())
103
- if has_cached_counter?(reflection)
104
- counter = cached_counter_attribute_name(reflection)
105
- owner[counter] += difference
106
- owner.send(:clear_attribute_changes, counter) # eww
97
+ if reflection.counter_must_be_updated_by_has_many?
98
+ counter = reflection.counter_cache_column
99
+ owner.increment(counter, difference)
100
+ owner.send(:clear_attribute_change, counter) # eww
107
101
  end
108
102
  end
109
103
 
110
- # This shit is nasty. We need to avoid the following situation:
111
- #
112
- # * An associated record is deleted via record.destroy
113
- # * Hence the callbacks run, and they find a belongs_to on the record with a
114
- # :counter_cache options which points back at our owner. So they update the
115
- # counter cache.
116
- # * In which case, we must make sure to *not* update the counter cache, or else
117
- # it will be decremented twice.
118
- #
119
- # Hence this method.
120
- def inverse_updates_counter_cache?(reflection = reflection())
121
- counter_name = cached_counter_attribute_name(reflection)
122
- inverse_updates_counter_named?(counter_name, reflection)
123
- end
124
-
125
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
126
- reflection.klass._reflections.values.any? { |inverse_reflection|
127
- inverse_reflection.belongs_to? &&
128
- inverse_reflection.counter_cache_column == counter_name
129
- }
130
- end
131
-
132
104
  def delete_count(method, scope)
133
105
  if method == :delete_all
134
106
  scope.delete_all
@@ -146,21 +118,13 @@ module ActiveRecord
146
118
  def delete_records(records, method)
147
119
  if method == :destroy
148
120
  records.each(&:destroy!)
149
- update_counter(-records.length) unless inverse_updates_counter_cache?
121
+ update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
150
122
  else
151
123
  scope = self.scope.where(reflection.klass.primary_key => records)
152
124
  update_counter(-delete_count(method, scope))
153
125
  end
154
126
  end
155
127
 
156
- def foreign_key_present?
157
- if reflection.klass.primary_key
158
- owner.attribute_present?(reflection.association_primary_key)
159
- else
160
- false
161
- end
162
- end
163
-
164
128
  def concat_records(records, *)
165
129
  update_counter_if_success(super, records.length)
166
130
  end