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
@@ -1,10 +1,10 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Autosave Association
3
3
  #
4
- # +AutosaveAssociation+ is a module that takes care of automatically saving
4
+ # AutosaveAssociation is a module that takes care of automatically saving
5
5
  # associated records when their parent is saved. In addition to saving, it
6
6
  # also destroys any associated records that were marked for destruction.
7
- # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
7
+ # (See #mark_for_destruction and #marked_for_destruction?).
8
8
  #
9
9
  # Saving of the parent, its associations, and the destruction of marked
10
10
  # associations, all happen inside a transaction. This should never leave the
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  #
23
23
  # == Validation
24
24
  #
25
- # Children records are validated unless <tt>:validate</tt> is +false+.
25
+ # Child records are validated unless <tt>:validate</tt> is +false+.
26
26
  #
27
27
  # == Callbacks
28
28
  #
@@ -125,7 +125,6 @@ module ActiveRecord
125
125
  # Now it _is_ removed from the database:
126
126
  #
127
127
  # Comment.find_by(id: id).nil? # => true
128
-
129
128
  module AutosaveAssociation
130
129
  extend ActiveSupport::Concern
131
130
 
@@ -141,9 +140,11 @@ module ActiveRecord
141
140
 
142
141
  included do
143
142
  Associations::Builder::Association.extensions << AssociationBuilderExtension
143
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
144
+ self.index_nested_attribute_errors = false
144
145
  end
145
146
 
146
- module ClassMethods
147
+ module ClassMethods # :nodoc:
147
148
  private
148
149
 
149
150
  def define_non_cyclic_method(name, &block)
@@ -177,10 +178,8 @@ module ActiveRecord
177
178
  # before actually defining them.
178
179
  def add_autosave_association_callbacks(reflection)
179
180
  save_method = :"autosave_associated_records_for_#{reflection.name}"
180
- validation_method = :"validate_associated_records_for_#{reflection.name}"
181
- collection = reflection.collection?
182
181
 
183
- if collection
182
+ if reflection.collection?
184
183
  before_save :before_save_collection_association
185
184
 
186
185
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
@@ -200,14 +199,31 @@ module ActiveRecord
200
199
  after_create save_method
201
200
  after_update save_method
202
201
  else
203
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
202
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
204
203
  before_save save_method
205
204
  end
206
205
 
206
+ define_autosave_validation_callbacks(reflection)
207
+ end
208
+
209
+ def define_autosave_validation_callbacks(reflection)
210
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
207
211
  if reflection.validate? && !method_defined?(validation_method)
208
- method = (collection ? :validate_collection_association : :validate_single_association)
209
- define_non_cyclic_method(validation_method) { send(method, reflection) }
212
+ if reflection.collection?
213
+ method = :validate_collection_association
214
+ else
215
+ method = :validate_single_association
216
+ end
217
+
218
+ define_non_cyclic_method(validation_method) do
219
+ send(method, reflection)
220
+ # TODO: remove the following line as soon as the return value of
221
+ # callbacks is ignored, that is, returning `false` does not
222
+ # display a deprecation warning or halts the callback chain.
223
+ true
224
+ end
210
225
  validate validation_method
226
+ after_validation :_ensure_no_duplicate_errors
211
227
  end
212
228
  end
213
229
  end
@@ -219,7 +235,7 @@ module ActiveRecord
219
235
  super
220
236
  end
221
237
 
222
- # Marks this record to be destroyed as part of the parents save transaction.
238
+ # Marks this record to be destroyed as part of the parent's save transaction.
223
239
  # This does _not_ actually destroy the record instantly, rather child record will be destroyed
224
240
  # when <tt>parent.save</tt> is called.
225
241
  #
@@ -228,7 +244,7 @@ module ActiveRecord
228
244
  @marked_for_destruction = true
229
245
  end
230
246
 
231
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
247
+ # Returns whether or not this record will be destroyed as part of the parent's save transaction.
232
248
  #
233
249
  # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
234
250
  def marked_for_destruction?
@@ -263,20 +279,27 @@ module ActiveRecord
263
279
  if new_record
264
280
  association && association.target
265
281
  elsif autosave
266
- association.target.find_all { |record| record.changed_for_autosave? }
282
+ association.target.find_all(&:changed_for_autosave?)
267
283
  else
268
- association.target.find_all { |record| record.new_record? }
284
+ association.target.find_all(&:new_record?)
269
285
  end
270
286
  end
271
287
 
272
288
  # go through nested autosave associations that are loaded in memory (without loading
273
289
  # any new ones), and return true if is changed for autosave
274
290
  def nested_records_changed_for_autosave?
275
- self.class._reflections.values.any? do |reflection|
276
- if reflection.options[:autosave]
277
- association = association_instance_get(reflection.name)
278
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
291
+ @_nested_records_changed_for_autosave_already_called ||= false
292
+ return false if @_nested_records_changed_for_autosave_already_called
293
+ begin
294
+ @_nested_records_changed_for_autosave_already_called = true
295
+ self.class._reflections.values.any? do |reflection|
296
+ if reflection.options[:autosave]
297
+ association = association_instance_get(reflection.name)
298
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
299
+ end
279
300
  end
301
+ ensure
302
+ @_nested_records_changed_for_autosave_already_called = false
280
303
  end
281
304
  end
282
305
 
@@ -294,7 +317,7 @@ module ActiveRecord
294
317
  def validate_collection_association(reflection)
295
318
  if association = association_instance_get(reflection.name)
296
319
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
297
- records.each { |record| association_valid?(reflection, record) }
320
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
298
321
  end
299
322
  end
300
323
  end
@@ -302,17 +325,36 @@ module ActiveRecord
302
325
  # Returns whether or not the association is valid and applies any errors to
303
326
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
304
327
  # enabled records if they're marked_for_destruction? or destroyed.
305
- def association_valid?(reflection, record)
306
- return true if record.destroyed? || record.marked_for_destruction?
328
+ def association_valid?(reflection, record, index=nil)
329
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
307
330
 
308
331
  validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
309
332
  unless valid = record.valid?(validation_context)
310
333
  if reflection.options[:autosave]
334
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
335
+
311
336
  record.errors.each do |attribute, message|
312
- attribute = "#{reflection.name}.#{attribute}"
337
+ if indexed_attribute
338
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
339
+ else
340
+ attribute = "#{reflection.name}.#{attribute}"
341
+ end
313
342
  errors[attribute] << message
314
343
  errors[attribute].uniq!
315
344
  end
345
+
346
+ record.errors.details.each_key do |attribute|
347
+ if indexed_attribute
348
+ reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
349
+ else
350
+ reflection_attribute = "#{reflection.name}.#{attribute}"
351
+ end
352
+
353
+ record.errors.details[attribute].each do |error|
354
+ errors.details[reflection_attribute] << error
355
+ errors.details[reflection_attribute].uniq!
356
+ end
357
+ end
316
358
  else
317
359
  errors.add(reflection.name)
318
360
  end
@@ -331,7 +373,7 @@ module ActiveRecord
331
373
  # <tt>:autosave</tt> is enabled on the association.
332
374
  #
333
375
  # In addition, it destroys all children that were marked for destruction
334
- # with mark_for_destruction.
376
+ # with #mark_for_destruction.
335
377
  #
336
378
  # This all happens inside a transaction, _if_ the Transactions module is included into
337
379
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -374,7 +416,7 @@ module ActiveRecord
374
416
  # on the association.
375
417
  #
376
418
  # In addition, it will destroy the association if it was marked for
377
- # destruction with mark_for_destruction.
419
+ # destruction with #mark_for_destruction.
378
420
  #
379
421
  # This all happens inside a transaction, _if_ the Transactions module is included into
380
422
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -435,5 +477,11 @@ module ActiveRecord
435
477
  end
436
478
  end
437
479
  end
480
+
481
+ def _ensure_no_duplicate_errors
482
+ errors.messages.each_key do |attribute|
483
+ errors[attribute].uniq!
484
+ end
485
+ end
438
486
  end
439
487
  end
@@ -1,11 +1,9 @@
1
1
  require 'yaml'
2
- require 'set'
3
2
  require 'active_support/benchmarkable'
4
3
  require 'active_support/dependencies'
5
4
  require 'active_support/descendants_tracker'
6
5
  require 'active_support/time'
7
6
  require 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/class/delegating_attributes'
9
7
  require 'active_support/core_ext/array/extract_options'
10
8
  require 'active_support/core_ext/hash/deep_merge'
11
9
  require 'active_support/core_ext/hash/slice'
@@ -15,13 +13,13 @@ require 'active_support/core_ext/kernel/singleton_class'
15
13
  require 'active_support/core_ext/module/introspection'
16
14
  require 'active_support/core_ext/object/duplicable'
17
15
  require 'active_support/core_ext/class/subclasses'
18
- require 'arel'
19
16
  require 'active_record/attribute_decorators'
20
17
  require 'active_record/errors'
21
18
  require 'active_record/log_subscriber'
22
19
  require 'active_record/explain_subscriber'
23
20
  require 'active_record/relation/delegation'
24
21
  require 'active_record/attributes'
22
+ require 'active_record/type_caster'
25
23
 
26
24
  module ActiveRecord #:nodoc:
27
25
  # = Active Record
@@ -119,29 +117,25 @@ module ActiveRecord #:nodoc:
119
117
  # All column values are automatically available through basic accessors on the Active Record
120
118
  # object, but sometimes you want to specialize this behavior. This can be done by overwriting
121
119
  # the default accessors (using the same name as the attribute) and calling
122
- # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
123
- # change things.
120
+ # +super+ to actually change things.
124
121
  #
125
122
  # class Song < ActiveRecord::Base
126
123
  # # Uses an integer of seconds to hold the length of the song
127
124
  #
128
125
  # def length=(minutes)
129
- # write_attribute(:length, minutes.to_i * 60)
126
+ # super(minutes.to_i * 60)
130
127
  # end
131
128
  #
132
129
  # def length
133
- # read_attribute(:length) / 60
130
+ # super / 60
134
131
  # end
135
132
  # end
136
133
  #
137
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
138
- # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
139
- #
140
134
  # == Attribute query methods
141
135
  #
142
136
  # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
143
137
  # Query methods allow you to test whether an attribute value is present.
144
- # For numeric values, present is defined as non-zero.
138
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
145
139
  #
146
140
  # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
147
141
  # to determine whether the user has a name:
@@ -172,10 +166,11 @@ module ActiveRecord #:nodoc:
172
166
  # <tt>Person.find_by_user_name(user_name)</tt>.
173
167
  #
174
168
  # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
175
- # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
169
+ # ActiveRecord::RecordNotFound error if they do not return any records,
176
170
  # like <tt>Person.find_by_last_name!</tt>.
177
171
  #
178
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
172
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
173
+ # "_and_".
179
174
  #
180
175
  # Person.find_by(user_name: user_name, password: password)
181
176
  # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
@@ -187,7 +182,8 @@ module ActiveRecord #:nodoc:
187
182
  # == Saving arrays, hashes, and other non-mappable objects in text columns
188
183
  #
189
184
  # Active Record can serialize any object in text columns using YAML. To do so, you must
190
- # specify this with a call to the class method +serialize+.
185
+ # specify this with a call to the class method
186
+ # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
191
187
  # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
192
188
  # any additional work.
193
189
  #
@@ -227,39 +223,47 @@ module ActiveRecord #:nodoc:
227
223
  #
228
224
  # == Connection to multiple databases in different models
229
225
  #
230
- # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
226
+ # Connections are usually created through
227
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
231
228
  # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
232
229
  # connection. But you can also set a class-specific connection. For example, if Course is an
233
230
  # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
234
231
  # and Course and all of its subclasses will use this connection instead.
235
232
  #
236
233
  # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
237
- # a Hash indexed by the class. If a connection is requested, the retrieve_connection method
234
+ # a hash indexed by the class. If a connection is requested, the
235
+ # {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
238
236
  # will go up the class-hierarchy until a connection is found in the connection pool.
239
237
  #
240
238
  # == Exceptions
241
239
  #
242
240
  # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
243
- # * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
244
- # <tt>:adapter</tt> key.
245
- # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
246
- # non-existent adapter
241
+ # * AdapterNotSpecified - The configuration hash used in
242
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
243
+ # didn't include an <tt>:adapter</tt> key.
244
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in
245
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
246
+ # specified a non-existent adapter
247
247
  # (or a bad spelling of an existing one).
248
248
  # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
249
249
  # specified in the association definition.
250
250
  # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
251
- # <tt>attributes=</tt> method.
251
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
252
252
  # You can inspect the +attribute+ property of the exception object to determine which attribute
253
253
  # triggered the error.
254
- # * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
255
- # before querying.
254
+ # * ConnectionNotEstablished - No connection has been established.
255
+ # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
256
256
  # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
257
- # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
257
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
258
+ # The +errors+ property of this exception contains an array of
258
259
  # AttributeAssignmentError
259
260
  # objects that should be inspected to determine which attributes triggered the errors.
260
- # * RecordInvalid - raised by save! and create! when the record is invalid.
261
- # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
262
- # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
261
+ # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
262
+ # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
263
+ # when the record is invalid.
264
+ # * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
265
+ # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
266
+ # Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
263
267
  # nothing was found, please check its documentation for further details.
264
268
  # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
265
269
  # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
@@ -281,6 +285,7 @@ module ActiveRecord #:nodoc:
281
285
  extend Explain
282
286
  extend Enum
283
287
  extend Delegation::DelegateCache
288
+ extend CollectionCacheKey
284
289
 
285
290
  include Core
286
291
  include Persistence
@@ -308,9 +313,12 @@ module ActiveRecord #:nodoc:
308
313
  include Aggregations
309
314
  include Transactions
310
315
  include NoTouching
316
+ include TouchLater
311
317
  include Reflection
312
318
  include Serialization
313
319
  include Store
320
+ include SecureToken
321
+ include Suppressor
314
322
  end
315
323
 
316
324
  ActiveSupport.run_load_hooks(:active_record, Base)
@@ -1,11 +1,11 @@
1
1
  module ActiveRecord
2
- # = Active Record Callbacks
2
+ # = Active Record \Callbacks
3
3
  #
4
- # Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
4
+ # \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
5
5
  # before or after an alteration of the object state. This can be used to make sure that associated and
6
- # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
7
- # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
8
- # the <tt>Base#save</tt> call for a new record:
6
+ # dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
7
+ # to massage attributes before they're validated (by overwriting +before_validation+).
8
+ # As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
9
9
  #
10
10
  # * (-) <tt>save</tt>
11
11
  # * (-) <tt>valid</tt>
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # * (7) <tt>after_commit</tt>
21
21
  #
22
22
  # Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
23
- # Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
23
+ # Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
24
24
  # <tt>after_rollback</tt>.
25
25
  #
26
26
  # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  # are instantiated as well.
32
32
  #
33
33
  # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
34
- # Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
34
+ # Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
35
35
  # except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
36
36
  #
37
37
  # Examples:
@@ -53,9 +53,9 @@ module ActiveRecord
53
53
  # end
54
54
  #
55
55
  # class Firm < ActiveRecord::Base
56
- # # Destroys the associated clients and people when the firm is destroyed
57
- # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
58
- # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
56
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
57
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
58
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
59
59
  # end
60
60
  #
61
61
  # == Inheritable callback queues
@@ -175,43 +175,30 @@ module ActiveRecord
175
175
  # end
176
176
  # end
177
177
  #
178
- # The callback macros usually accept a symbol for the method they're supposed to run, but you can also
179
- # pass a "method string", which will then be evaluated within the binding of the callback. Example:
180
- #
181
- # class Topic < ActiveRecord::Base
182
- # before_destroy 'self.class.delete_all "parent_id = #{id}"'
183
- # end
184
- #
185
- # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
186
- # is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
187
- #
188
- # class Topic < ActiveRecord::Base
189
- # before_destroy 'self.class.delete_all "parent_id = #{id}"',
190
- # 'puts "Evaluated after parents are destroyed"'
191
- # end
192
- #
193
178
  # == <tt>before_validation*</tt> returning statements
194
179
  #
195
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
196
- # aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
197
- # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
180
+ # If the +before_validation+ callback throws +:abort+, the process will be
181
+ # aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
182
+ # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
183
+ # Nothing will be appended to the errors object.
198
184
  #
199
185
  # == Canceling callbacks
200
186
  #
201
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
202
- # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
187
+ # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
188
+ # the associated action are cancelled.
203
189
  # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
204
190
  # methods on the model, which are called last.
205
191
  #
206
192
  # == Ordering callbacks
207
193
  #
208
194
  # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
209
- # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
195
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the
196
+ # <tt>dependent: :destroy</tt> option.
210
197
  #
211
198
  # Let's look at the code below:
212
199
  #
213
200
  # class Topic < ActiveRecord::Base
214
- # has_many :children, dependent: destroy
201
+ # has_many :children, dependent: :destroy
215
202
  #
216
203
  # before_destroy :log_children
217
204
  #
@@ -222,10 +209,11 @@ module ActiveRecord
222
209
  # end
223
210
  #
224
211
  # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
225
- # because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
212
+ # because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback gets executed first.
213
+ # You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
226
214
  #
227
215
  # class Topic < ActiveRecord::Base
228
- # has_many :children, dependent: destroy
216
+ # has_many :children, dependent: :destroy
229
217
  #
230
218
  # before_destroy :log_children, prepend: true
231
219
  #
@@ -235,23 +223,23 @@ module ActiveRecord
235
223
  # end
236
224
  # end
237
225
  #
238
- # This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
226
+ # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
239
227
  #
240
- # == Transactions
228
+ # == \Transactions
241
229
  #
242
- # The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
243
- # within a transaction. That includes <tt>after_*</tt> hooks. If everything
244
- # goes fine a COMMIT is executed once the chain has been completed.
230
+ # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
231
+ # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
232
+ # If everything goes fine a COMMIT is executed once the chain has been completed.
245
233
  #
246
234
  # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
247
235
  # can also trigger a ROLLBACK raising an exception in any of the callbacks,
248
236
  # including <tt>after_*</tt> hooks. Note, however, that in that case the client
249
- # needs to be aware of it because an ordinary +save+ will raise such exception
237
+ # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
250
238
  # instead of quietly returning +false+.
251
239
  #
252
240
  # == Debugging callbacks
253
241
  #
254
- # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
242
+ # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
255
243
  # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
256
244
  # defines what part of the chain the callback runs in.
257
245
  #
@@ -277,7 +265,7 @@ module ActiveRecord
277
265
  :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
278
266
  ]
279
267
 
280
- module ClassMethods
268
+ module ClassMethods # :nodoc:
281
269
  include ActiveModel::Callbacks
282
270
  end
283
271
 
@@ -289,7 +277,15 @@ module ActiveRecord
289
277
  end
290
278
 
291
279
  def destroy #:nodoc:
280
+ @_destroy_callback_already_called ||= false
281
+ return if @_destroy_callback_already_called
282
+ @_destroy_callback_already_called = true
292
283
  _run_destroy_callbacks { super }
284
+ rescue RecordNotDestroyed => e
285
+ @_association_destroy_exception = e
286
+ false
287
+ ensure
288
+ @_destroy_callback_already_called = false
293
289
  end
294
290
 
295
291
  def touch(*) #:nodoc:
@@ -298,7 +294,7 @@ module ActiveRecord
298
294
 
299
295
  private
300
296
 
301
- def create_or_update #:nodoc:
297
+ def create_or_update(*) #:nodoc:
302
298
  _run_save_callbacks { super }
303
299
  end
304
300
 
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  end
7
7
 
8
8
  def self.load(json)
9
- ActiveSupport::JSON.decode(json) unless json.nil?
9
+ ActiveSupport::JSON.decode(json) unless json.blank?
10
10
  end
11
11
  end
12
12
  end
@@ -8,15 +8,13 @@ module ActiveRecord
8
8
 
9
9
  def initialize(object_class = Object)
10
10
  @object_class = object_class
11
+ check_arity_of_constructor
11
12
  end
12
13
 
13
14
  def dump(obj)
14
15
  return if obj.nil?
15
16
 
16
- unless obj.is_a?(object_class)
17
- raise SerializationTypeMismatch,
18
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
19
- end
17
+ assert_valid_value(obj)
20
18
  YAML.dump obj
21
19
  end
22
20
 
@@ -25,14 +23,28 @@ module ActiveRecord
25
23
  return yaml unless yaml.is_a?(String) && yaml =~ /^---/
26
24
  obj = YAML.load(yaml)
27
25
 
28
- unless obj.is_a?(object_class) || obj.nil?
29
- raise SerializationTypeMismatch,
30
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
31
- end
26
+ assert_valid_value(obj)
32
27
  obj ||= object_class.new if object_class != Object
33
28
 
34
29
  obj
35
30
  end
31
+
32
+ def assert_valid_value(obj)
33
+ unless obj.nil? || obj.is_a?(object_class)
34
+ raise SerializationTypeMismatch,
35
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def check_arity_of_constructor
42
+ begin
43
+ load(nil)
44
+ rescue ArgumentError
45
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
+ end
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ module CollectionCacheKey
3
+
4
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
5
+ query_signature = Digest::MD5.hexdigest(collection.to_sql)
6
+ key = "#{collection.model_name.cache_key}/query-#{query_signature}"
7
+
8
+ if collection.loaded?
9
+ size = collection.size
10
+ if size > 0
11
+ timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
12
+ end
13
+ else
14
+ column_type = type_for_attribute(timestamp_column.to_s)
15
+ column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
16
+
17
+ query = collection
18
+ .unscope(:select)
19
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
20
+ .unscope(:order)
21
+ result = connection.select_one(query)
22
+
23
+ if result.blank?
24
+ size = 0
25
+ timestamp = nil
26
+ else
27
+ size = result["size"]
28
+ timestamp = column_type.deserialize(result["timestamp"])
29
+ end
30
+
31
+ end
32
+
33
+ if timestamp
34
+ "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
35
+ else
36
+ "#{key}-#{size}"
37
+ end
38
+ end
39
+ end
40
+ end