activerecord 6.0.0 → 6.1.3

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1045 -575
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_record.rb +7 -13
  6. data/lib/active_record/aggregations.rb +5 -6
  7. data/lib/active_record/association_relation.rb +30 -10
  8. data/lib/active_record/associations.rb +120 -13
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +49 -29
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +25 -8
  22. data/lib/active_record/associations/collection_proxy.rb +14 -7
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -3
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -9
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  38. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -12
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +12 -21
  45. data/lib/active_record/attributes.rb +33 -9
  46. data/lib/active_record/autosave_association.rb +63 -44
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +153 -24
  49. data/lib/active_record/coders/yaml_column.rb +1 -2
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +87 -38
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -10
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +141 -52
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -77
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  77. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  97. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  99. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  100. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  101. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  102. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  103. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  104. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  105. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -65
  106. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  107. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  108. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  109. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  110. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  111. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  112. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  113. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  114. data/lib/active_record/connection_handling.rb +219 -81
  115. data/lib/active_record/core.rb +253 -67
  116. data/lib/active_record/counter_cache.rb +4 -1
  117. data/lib/active_record/database_configurations.rb +124 -85
  118. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  119. data/lib/active_record/database_configurations/database_config.rb +52 -9
  120. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  121. data/lib/active_record/database_configurations/url_config.rb +15 -41
  122. data/lib/active_record/delegated_type.rb +209 -0
  123. data/lib/active_record/destroy_association_async_job.rb +36 -0
  124. data/lib/active_record/dynamic_matchers.rb +2 -3
  125. data/lib/active_record/enum.rb +82 -38
  126. data/lib/active_record/errors.rb +47 -12
  127. data/lib/active_record/explain.rb +9 -5
  128. data/lib/active_record/explain_subscriber.rb +1 -1
  129. data/lib/active_record/fixture_set/file.rb +10 -17
  130. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  131. data/lib/active_record/fixture_set/render_context.rb +1 -1
  132. data/lib/active_record/fixture_set/table_row.rb +2 -3
  133. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  134. data/lib/active_record/fixtures.rb +58 -12
  135. data/lib/active_record/gem_version.rb +2 -2
  136. data/lib/active_record/inheritance.rb +40 -21
  137. data/lib/active_record/insert_all.rb +39 -10
  138. data/lib/active_record/integration.rb +3 -5
  139. data/lib/active_record/internal_metadata.rb +16 -7
  140. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  141. data/lib/active_record/locking/optimistic.rb +33 -18
  142. data/lib/active_record/locking/pessimistic.rb +6 -2
  143. data/lib/active_record/log_subscriber.rb +28 -9
  144. data/lib/active_record/middleware/database_selector.rb +4 -2
  145. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  146. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  147. data/lib/active_record/migration.rb +115 -85
  148. data/lib/active_record/migration/command_recorder.rb +53 -45
  149. data/lib/active_record/migration/compatibility.rb +71 -20
  150. data/lib/active_record/migration/join_table.rb +0 -1
  151. data/lib/active_record/model_schema.rb +120 -15
  152. data/lib/active_record/nested_attributes.rb +2 -5
  153. data/lib/active_record/no_touching.rb +1 -1
  154. data/lib/active_record/null_relation.rb +0 -1
  155. data/lib/active_record/persistence.rb +50 -46
  156. data/lib/active_record/query_cache.rb +15 -5
  157. data/lib/active_record/querying.rb +12 -7
  158. data/lib/active_record/railtie.rb +65 -45
  159. data/lib/active_record/railties/console_sandbox.rb +2 -4
  160. data/lib/active_record/railties/databases.rake +277 -97
  161. data/lib/active_record/readonly_attributes.rb +4 -0
  162. data/lib/active_record/reflection.rb +77 -63
  163. data/lib/active_record/relation.rb +107 -67
  164. data/lib/active_record/relation/batches.rb +38 -32
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  166. data/lib/active_record/relation/calculations.rb +102 -45
  167. data/lib/active_record/relation/delegation.rb +9 -7
  168. data/lib/active_record/relation/finder_methods.rb +55 -17
  169. data/lib/active_record/relation/from_clause.rb +5 -1
  170. data/lib/active_record/relation/merger.rb +27 -26
  171. data/lib/active_record/relation/predicate_builder.rb +59 -40
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  174. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  175. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  176. data/lib/active_record/relation/query_methods.rb +343 -180
  177. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  178. data/lib/active_record/relation/spawn_methods.rb +8 -8
  179. data/lib/active_record/relation/where_clause.rb +107 -61
  180. data/lib/active_record/result.rb +41 -34
  181. data/lib/active_record/runtime_registry.rb +2 -2
  182. data/lib/active_record/sanitization.rb +6 -17
  183. data/lib/active_record/schema_dumper.rb +34 -4
  184. data/lib/active_record/schema_migration.rb +2 -8
  185. data/lib/active_record/scoping.rb +0 -1
  186. data/lib/active_record/scoping/default.rb +0 -1
  187. data/lib/active_record/scoping/named.rb +7 -18
  188. data/lib/active_record/secure_token.rb +16 -8
  189. data/lib/active_record/serialization.rb +5 -3
  190. data/lib/active_record/signed_id.rb +116 -0
  191. data/lib/active_record/statement_cache.rb +20 -4
  192. data/lib/active_record/store.rb +3 -3
  193. data/lib/active_record/suppressor.rb +2 -2
  194. data/lib/active_record/table_metadata.rb +42 -36
  195. data/lib/active_record/tasks/database_tasks.rb +140 -113
  196. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  197. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  198. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  199. data/lib/active_record/test_databases.rb +5 -4
  200. data/lib/active_record/test_fixtures.rb +38 -16
  201. data/lib/active_record/timestamp.rb +4 -7
  202. data/lib/active_record/touch_later.rb +20 -21
  203. data/lib/active_record/transactions.rb +26 -73
  204. data/lib/active_record/type.rb +8 -2
  205. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  206. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  207. data/lib/active_record/type/serialized.rb +6 -3
  208. data/lib/active_record/type/time.rb +10 -0
  209. data/lib/active_record/type/type_map.rb +0 -1
  210. data/lib/active_record/type/unsigned_integer.rb +0 -1
  211. data/lib/active_record/type_caster/connection.rb +0 -1
  212. data/lib/active_record/type_caster/map.rb +8 -5
  213. data/lib/active_record/validations.rb +3 -3
  214. data/lib/active_record/validations/associated.rb +1 -2
  215. data/lib/active_record/validations/numericality.rb +35 -0
  216. data/lib/active_record/validations/uniqueness.rb +24 -4
  217. data/lib/arel.rb +15 -12
  218. data/lib/arel/attributes/attribute.rb +4 -0
  219. data/lib/arel/collectors/bind.rb +5 -0
  220. data/lib/arel/collectors/composite.rb +8 -0
  221. data/lib/arel/collectors/sql_string.rb +7 -0
  222. data/lib/arel/collectors/substitute_binds.rb +7 -0
  223. data/lib/arel/nodes.rb +3 -1
  224. data/lib/arel/nodes/binary.rb +82 -8
  225. data/lib/arel/nodes/bind_param.rb +8 -0
  226. data/lib/arel/nodes/casted.rb +21 -9
  227. data/lib/arel/nodes/equality.rb +6 -9
  228. data/lib/arel/nodes/grouping.rb +3 -0
  229. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  230. data/lib/arel/nodes/in.rb +8 -1
  231. data/lib/arel/nodes/infix_operation.rb +13 -1
  232. data/lib/arel/nodes/join_source.rb +1 -1
  233. data/lib/arel/nodes/node.rb +7 -6
  234. data/lib/arel/nodes/ordering.rb +27 -0
  235. data/lib/arel/nodes/sql_literal.rb +3 -0
  236. data/lib/arel/nodes/table_alias.rb +7 -3
  237. data/lib/arel/nodes/unary.rb +0 -1
  238. data/lib/arel/predications.rb +17 -24
  239. data/lib/arel/select_manager.rb +1 -2
  240. data/lib/arel/table.rb +13 -5
  241. data/lib/arel/visitors.rb +0 -7
  242. data/lib/arel/visitors/dot.rb +14 -3
  243. data/lib/arel/visitors/mysql.rb +11 -1
  244. data/lib/arel/visitors/postgresql.rb +15 -5
  245. data/lib/arel/visitors/sqlite.rb +0 -1
  246. data/lib/arel/visitors/to_sql.rb +89 -79
  247. data/lib/arel/visitors/visitor.rb +0 -1
  248. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  249. data/lib/rails/generators/active_record/migration.rb +6 -2
  250. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  251. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  252. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  253. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  254. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  255. metadata +30 -27
  256. data/lib/active_record/attribute_decorators.rb +0 -90
  257. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  258. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  259. data/lib/active_record/define_callbacks.rb +0 -22
  260. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  261. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  262. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  263. data/lib/arel/attributes.rb +0 -22
  264. data/lib/arel/visitors/depth_first.rb +0 -204
  265. data/lib/arel/visitors/ibm_db.rb +0 -34
  266. data/lib/arel/visitors/informix.rb +0 -62
  267. data/lib/arel/visitors/mssql.rb +0 -157
  268. data/lib/arel/visitors/oracle.rb +0 -159
  269. data/lib/arel/visitors/oracle12.rb +0 -66
  270. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -49,7 +49,7 @@ module ActiveRecord
49
49
  # +to+ When passed, this method will return false unless the value was
50
50
  # changed to the given value
51
51
  def saved_change_to_attribute?(attr_name, **options)
52
- mutations_before_last_save.changed?(attr_name.to_s, options)
52
+ mutations_before_last_save.changed?(attr_name.to_s, **options)
53
53
  end
54
54
 
55
55
  # Returns the change to an attribute during the last save. If the
@@ -89,7 +89,7 @@ module ActiveRecord
89
89
  # This method is useful in validations and before callbacks to determine
90
90
  # if the next call to +save+ will change a particular attribute. It can be
91
91
  # invoked as +will_save_change_to_name?+ instead of
92
- # <tt>will_save_change_to_attribute("name")</tt>.
92
+ # <tt>will_save_change_to_attribute?("name")</tt>.
93
93
  #
94
94
  # ==== Options
95
95
  #
@@ -99,7 +99,7 @@ module ActiveRecord
99
99
  # +to+ When passed, this method will return false unless the value will be
100
100
  # changed to the given value
101
101
  def will_save_change_to_attribute?(attr_name, **options)
102
- mutations_from_database.changed?(attr_name.to_s, options)
102
+ mutations_from_database.changed?(attr_name.to_s, **options)
103
103
  end
104
104
 
105
105
  # Returns the change to an attribute that will be persisted during the
@@ -156,16 +156,6 @@ module ActiveRecord
156
156
  end
157
157
 
158
158
  private
159
- def mutations_from_database
160
- sync_with_transaction_state if @transaction_state&.finalized?
161
- super
162
- end
163
-
164
- def mutations_before_last_save
165
- sync_with_transaction_state if @transaction_state&.finalized?
166
- super
167
- end
168
-
169
159
  def write_attribute_without_type_cast(attr_name, value)
170
160
  result = super
171
161
  clear_attribute_change(attr_name)
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
 
32
32
  # Returns the primary key column's value before type cast.
33
33
  def id_before_type_cast
34
- read_attribute_before_type_cast(@primary_key)
34
+ attribute_before_type_cast(@primary_key)
35
35
  end
36
36
 
37
37
  # Returns the primary key column's previous value.
@@ -44,14 +44,17 @@ module ActiveRecord
44
44
  attribute_in_database(@primary_key)
45
45
  end
46
46
 
47
- private
47
+ def id_for_database # :nodoc:
48
+ @attributes[@primary_key].value_for_database
49
+ end
48
50
 
51
+ private
49
52
  def attribute_method?(attr_name)
50
53
  attr_name == "id" || super
51
54
  end
52
55
 
53
56
  module ClassMethods
54
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
57
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
55
58
 
56
59
  def instance_method_already_implemented?(method_name)
57
60
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -120,7 +123,6 @@ module ActiveRecord
120
123
  end
121
124
 
122
125
  private
123
-
124
126
  def suppress_composite_primary_key(pk)
125
127
  return pk unless pk.is_a?(Array)
126
128
 
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  when false, nil then false
18
18
  else
19
19
  if !type_for_attribute(attr_name) { false }
20
- if Numeric === value || value !~ /[^0-9]/
20
+ if Numeric === value || !value.match?(/[^0-9]/)
21
21
  !value.to_i.zero?
22
22
  else
23
23
  return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
@@ -31,11 +31,8 @@ module ActiveRecord
31
31
  end
32
32
  end
33
33
 
34
- private
35
- # Dispatch target for <tt>*?</tt> attribute methods.
36
- def attribute?(attribute_name)
37
- query_attribute(attribute_name)
38
- end
34
+ alias :attribute? :query_attribute
35
+ private :attribute?
39
36
  end
40
37
  end
41
38
  end
@@ -7,17 +7,14 @@ module ActiveRecord
7
7
 
8
8
  module ClassMethods # :nodoc:
9
9
  private
10
-
11
- def define_method_attribute(name)
10
+ def define_method_attribute(name, owner:)
12
11
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
- generated_attribute_methods, name
12
+ owner, name
14
13
  ) do |temp_method_name, attr_name_expr|
15
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
16
- def #{temp_method_name}
17
- name = #{attr_name_expr}
18
- _read_attribute(name) { |n| missing_attribute(n, caller) }
19
- end
20
- RUBY
14
+ owner <<
15
+ "def #{temp_method_name}" <<
16
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
+ "end"
21
18
  end
22
19
  end
23
20
  end
@@ -30,14 +27,13 @@ module ActiveRecord
30
27
  name = self.class.attribute_aliases[name] || name
31
28
 
32
29
  name = @primary_key if name == "id" && @primary_key
33
- _read_attribute(name, &block)
30
+ @attributes.fetch_value(name, &block)
34
31
  end
35
32
 
36
33
  # This method exists to avoid the expensive primary_key check internally, without
37
34
  # breaking compatibility with the read_attribute API
38
35
  def _read_attribute(attr_name, &block) # :nodoc
39
- sync_with_transaction_state if @transaction_state&.finalized?
40
- @attributes.fetch_value(attr_name.to_s, &block)
36
+ @attributes.fetch_value(attr_name, &block)
41
37
  end
42
38
 
43
39
  alias :attribute :_read_attribute
@@ -41,6 +41,12 @@ module ActiveRecord
41
41
  # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
42
42
  # or a class name that the object type should be equal to.
43
43
  #
44
+ # ==== Options
45
+ #
46
+ # +default+ The default value to use when no value is provided. If this option
47
+ # is not passed, the previous default value (if any) will be used.
48
+ # Otherwise, the default will be +nil+.
49
+ #
44
50
  # ==== Example
45
51
  #
46
52
  # # Serialize a preferences attribute.
@@ -57,7 +63,7 @@ module ActiveRecord
57
63
  # class User < ActiveRecord::Base
58
64
  # serialize :preferences, Hash
59
65
  # end
60
- def serialize(attr_name, class_name_or_coder = Object)
66
+ def serialize(attr_name, class_name_or_coder = Object, **options)
61
67
  # When ::JSON is used, force it to go through the Active Support JSON encoder
62
68
  # to ensure special objects (e.g. Active Record models) are dumped correctly
63
69
  # using the #as_json hook.
@@ -69,17 +75,16 @@ module ActiveRecord
69
75
  Coders::YAMLColumn.new(attr_name, class_name_or_coder)
70
76
  end
71
77
 
72
- decorate_attribute_type(attr_name, :serialize) do |type|
73
- if type_incompatible_with_serialize?(type, class_name_or_coder)
74
- raise ColumnNotSerializableError.new(attr_name, type)
78
+ decorate_attribute_type(attr_name.to_s, **options) do |cast_type|
79
+ if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
80
+ raise ColumnNotSerializableError.new(attr_name, cast_type)
75
81
  end
76
82
 
77
- Type::Serialized.new(type, coder)
83
+ Type::Serialized.new(cast_type, coder)
78
84
  end
79
85
  end
80
86
 
81
87
  private
82
-
83
88
  def type_incompatible_with_serialize?(type, class_name)
84
89
  type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
85
90
  type.respond_to?(:type_cast_array, true) && class_name == ::Array
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+
3
5
  module ActiveRecord
4
6
  module AttributeMethods
5
7
  module TimeZoneConversion
6
8
  class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
9
+ def self.new(subtype)
10
+ self === subtype ? subtype : super
11
+ end
12
+
7
13
  def deserialize(value)
8
14
  convert_time_to_time_zone(super)
9
15
  end
@@ -25,7 +31,6 @@ module ActiveRecord
25
31
  end
26
32
 
27
33
  private
28
-
29
34
  def convert_time_to_time_zone(value)
30
35
  return if value.nil?
31
36
 
@@ -63,22 +68,14 @@ module ActiveRecord
63
68
  end
64
69
 
65
70
  module ClassMethods # :nodoc:
66
- private
67
-
68
- def inherited(subclass)
69
- super
70
- # We need to apply this decorator here, rather than on module inclusion. The closure
71
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
72
- # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
73
- # `skip_time_zone_conversion_for_attributes` would not be picked up.
74
- subclass.class_eval do
75
- matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
76
- decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
77
- TimeZoneConverter.new(type)
78
- end
79
- end
71
+ def define_attribute(name, cast_type, **)
72
+ if create_time_zone_conversion_attribute?(name, cast_type)
73
+ cast_type = TimeZoneConverter.new(cast_type)
80
74
  end
75
+ super
76
+ end
81
77
 
78
+ private
82
79
  def create_time_zone_conversion_attribute?(name, cast_type)
83
80
  enabled_for_column = time_zone_aware_attributes &&
84
81
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -11,17 +11,14 @@ module ActiveRecord
11
11
 
12
12
  module ClassMethods # :nodoc:
13
13
  private
14
-
15
- def define_method_attribute=(name)
14
+ def define_method_attribute=(name, owner:)
16
15
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
- generated_attribute_methods, name, writer: true,
16
+ owner, name, writer: true,
18
17
  ) do |temp_method_name, attr_name_expr|
19
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- def #{temp_method_name}(value)
21
- name = #{attr_name_expr}
22
- _write_attribute(name, value)
23
- end
24
- RUBY
18
+ owner <<
19
+ "def #{temp_method_name}(value)" <<
20
+ " _write_attribute(#{attr_name_expr}, value)" <<
21
+ "end"
25
22
  end
26
23
  end
27
24
  end
@@ -34,27 +31,21 @@ module ActiveRecord
34
31
  name = self.class.attribute_aliases[name] || name
35
32
 
36
33
  name = @primary_key if name == "id" && @primary_key
37
- _write_attribute(name, value)
34
+ @attributes.write_from_user(name, value)
38
35
  end
39
36
 
40
37
  # This method exists to avoid the expensive primary_key check internally, without
41
38
  # breaking compatibility with the write_attribute API
42
39
  def _write_attribute(attr_name, value) # :nodoc:
43
- sync_with_transaction_state if @transaction_state&.finalized?
44
- @attributes.write_from_user(attr_name.to_s, value)
45
- value
40
+ @attributes.write_from_user(attr_name, value)
46
41
  end
47
42
 
43
+ alias :attribute= :_write_attribute
44
+ private :attribute=
45
+
48
46
  private
49
47
  def write_attribute_without_type_cast(attr_name, value)
50
- sync_with_transaction_state if @transaction_state&.finalized?
51
- @attributes.write_cast_value(attr_name.to_s, value)
52
- value
53
- end
54
-
55
- # Dispatch target for <tt>*=</tt> attribute methods.
56
- def attribute=(attribute_name, value)
57
- _write_attribute(attribute_name, value)
48
+ @attributes.write_cast_value(attr_name, value)
58
49
  end
59
50
  end
60
51
  end
@@ -12,6 +12,9 @@ module ActiveRecord
12
12
  end
13
13
 
14
14
  module ClassMethods
15
+ ##
16
+ # :call-seq: attribute(name, cast_type = nil, **options)
17
+ #
15
18
  # Defines an attribute with a type on this model. It will override the
16
19
  # type of existing attributes if needed. This allows control over how
17
20
  # values are converted to and from SQL when assigned to a model. It also
@@ -170,7 +173,7 @@ module ActiveRecord
170
173
  # class Money < Struct.new(:amount, :currency)
171
174
  # end
172
175
  #
173
- # class MoneyType < Type::Value
176
+ # class MoneyType < ActiveRecord::Type::Value
174
177
  # def initialize(currency_converter:)
175
178
  # @currency_converter = currency_converter
176
179
  # end
@@ -205,13 +208,13 @@ module ActiveRecord
205
208
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
206
209
  # will be called from ActiveModel::Dirty. See the documentation for those
207
210
  # methods in ActiveModel::Type::Value for more details.
208
- def attribute(name, cast_type = Type::Value.new, **options)
211
+ def attribute(name, cast_type = nil, **options, &block)
209
212
  name = name.to_s
210
213
  reload_schema_from_cache
211
214
 
212
215
  self.attributes_to_define_after_schema_loads =
213
216
  attributes_to_define_after_schema_loads.merge(
214
- name => [cast_type, options]
217
+ name => [cast_type || block, options]
215
218
  )
216
219
  end
217
220
 
@@ -246,16 +249,11 @@ module ActiveRecord
246
249
  def load_schema! # :nodoc:
247
250
  super
248
251
  attributes_to_define_after_schema_loads.each do |name, (type, options)|
249
- if type.is_a?(Symbol)
250
- type = ActiveRecord::Type.lookup(type, **options.except(:default))
251
- end
252
-
253
- define_attribute(name, type, **options.slice(:default))
252
+ define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
254
253
  end
255
254
  end
256
255
 
257
256
  private
258
-
259
257
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
260
258
  private_constant :NO_DEFAULT_PROVIDED
261
259
 
@@ -274,6 +272,32 @@ module ActiveRecord
274
272
  end
275
273
  _default_attributes[name] = default_attribute
276
274
  end
275
+
276
+ def decorate_attribute_type(attr_name, **default)
277
+ type, options = attributes_to_define_after_schema_loads[attr_name]
278
+
279
+ default.with_defaults!(default: options[:default]) if options&.key?(:default)
280
+
281
+ attribute(attr_name, **default) do |cast_type|
282
+ if type && !type.is_a?(Proc)
283
+ cast_type = _lookup_cast_type(attr_name, type, options)
284
+ end
285
+
286
+ yield cast_type
287
+ end
288
+ end
289
+
290
+ def _lookup_cast_type(name, type, options)
291
+ case type
292
+ when Symbol
293
+ adapter_name = ActiveRecord::Type.adapter_name_from(self)
294
+ ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name)
295
+ when Proc
296
+ type[type_for_attribute(name)]
297
+ else
298
+ type || type_for_attribute(name)
299
+ end
300
+ end
277
301
  end
278
302
  end
279
303
  end
@@ -29,9 +29,9 @@ module ActiveRecord
29
29
  # == Callbacks
30
30
  #
31
31
  # Association with autosave option defines several callbacks on your
32
- # model (before_save, after_create, after_update). Please note that
32
+ # model (around_save, before_save, after_create, after_update). Please note that
33
33
  # callbacks are executed in the order they were defined in
34
- # model. You should avoid modifying the association content, before
34
+ # model. You should avoid modifying the association content before
35
35
  # autosave callbacks are executed. Placing your callbacks after
36
36
  # associations is usually a good practice.
37
37
  #
@@ -91,8 +91,9 @@ module ActiveRecord
91
91
  # post.save # => saves both post and comment
92
92
  #
93
93
  # post = Post.create(title: 'ruby rocks')
94
- # post.comments.create(body: 'hello world')
95
- # post.save # => saves both post and comment
94
+ # comment = post.comments.create(body: 'hello world')
95
+ # comment.body = 'hi everyone'
96
+ # post.save # => saves post, but not comment
96
97
  #
97
98
  # When <tt>:autosave</tt> is true all children are saved, no matter whether they
98
99
  # are new records or not:
@@ -102,11 +103,10 @@ module ActiveRecord
102
103
  # end
103
104
  #
104
105
  # post = Post.create(title: 'ruby rocks')
105
- # post.comments.create(body: 'hello world')
106
- # post.comments[0].body = 'hi everyone'
106
+ # comment = post.comments.create(body: 'hello world')
107
+ # comment.body = 'hi everyone'
107
108
  # post.comments.build(body: "good morning.")
108
- # post.title += "!"
109
- # post.save # => saves both post and comments.
109
+ # post.save # => saves post and both comments.
110
110
  #
111
111
  # Destroying one of the associated models as part of the parent's save action
112
112
  # is as simple as marking it for destruction:
@@ -127,6 +127,14 @@ module ActiveRecord
127
127
  # Now it _is_ removed from the database:
128
128
  #
129
129
  # Comment.find_by(id: id).nil? # => true
130
+ #
131
+ # === Caveats
132
+ #
133
+ # Note that autosave will only trigger for already-persisted association records
134
+ # if the records themselves have been changed. This is to protect against
135
+ # <tt>SystemStackError</tt> caused by circular association validations. The one
136
+ # exception is if a custom validation context is used, in which case the validations
137
+ # will always fire on the associated records.
130
138
  module AutosaveAssociation
131
139
  extend ActiveSupport::Concern
132
140
 
@@ -147,9 +155,23 @@ module ActiveRecord
147
155
 
148
156
  module ClassMethods # :nodoc:
149
157
  private
158
+ if Module.method(:method_defined?).arity == 1 # MRI 2.5 and older
159
+ using Module.new {
160
+ refine Module do
161
+ def method_defined?(method, inherit = true)
162
+ if inherit
163
+ super(method)
164
+ else
165
+ instance_methods(false).include?(method.to_sym)
166
+ end
167
+ end
168
+ end
169
+ }
170
+ end
150
171
 
151
172
  def define_non_cyclic_method(name, &block)
152
- return if instance_methods(false).include?(name)
173
+ return if method_defined?(name, false)
174
+
153
175
  define_method(name) do |*args|
154
176
  result = true; @_already_called ||= {}
155
177
  # Loop prevention for validation of associations
@@ -181,8 +203,7 @@ module ActiveRecord
181
203
  save_method = :"autosave_associated_records_for_#{reflection.name}"
182
204
 
183
205
  if reflection.collection?
184
- before_save :before_save_collection_association
185
- after_save :after_save_collection_association
206
+ around_save :around_save_collection_association
186
207
 
187
208
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
188
209
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
@@ -267,12 +288,11 @@ module ActiveRecord
267
288
  end
268
289
 
269
290
  private
270
-
271
291
  # Returns the record for an association collection that should be validated
272
292
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
293
  # unless the parent is/was a new record itself.
274
294
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
295
+ if new_record || custom_validation_context?
276
296
  association && association.target
277
297
  elsif autosave
278
298
  association.target.find_all(&:changed_for_autosave?)
@@ -281,8 +301,9 @@ module ActiveRecord
281
301
  end
282
302
  end
283
303
 
284
- # go through nested autosave associations that are loaded in memory (without loading
285
- # any new ones), and return true if is changed for autosave
304
+ # Go through nested autosave associations that are loaded in memory (without loading
305
+ # any new ones), and return true if any are changed for autosave.
306
+ # Returns false if already called to prevent an infinite loop.
286
307
  def nested_records_changed_for_autosave?
287
308
  @_nested_records_changed_for_autosave_already_called ||= false
288
309
  return false if @_nested_records_changed_for_autosave_already_called
@@ -304,7 +325,7 @@ module ActiveRecord
304
325
  def validate_single_association(reflection)
305
326
  association = association_instance_get(reflection.name)
306
327
  record = association && association.reader
307
- association_valid?(reflection, record) if record && record.changed_for_autosave?
328
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
329
  end
309
330
 
310
331
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,27 +345,22 @@ module ActiveRecord
324
345
  def association_valid?(reflection, record, index = nil)
325
346
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
326
347
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
348
+ context = validation_context if custom_validation_context?
328
349
 
329
350
  unless valid = record.valid?(context)
330
351
  if reflection.options[:autosave]
331
352
  indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
332
353
 
333
- record.errors.each do |attribute, message|
354
+ record.errors.group_by_attribute.each { |attribute, errors|
334
355
  attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
335
- errors[attribute] << message
336
- errors[attribute].uniq!
337
- end
338
-
339
- record.errors.details.each_key do |attribute|
340
- reflection_attribute =
341
- normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
342
356
 
343
- record.errors.details[attribute].each do |error|
344
- errors.details[reflection_attribute] << error
345
- errors.details[reflection_attribute].uniq!
346
- end
347
- end
357
+ errors.each { |error|
358
+ self.errors.import(
359
+ error,
360
+ attribute: attribute
361
+ )
362
+ }
363
+ }
348
364
  else
349
365
  errors.add(reflection.name)
350
366
  end
@@ -360,14 +376,15 @@ module ActiveRecord
360
376
  end
361
377
  end
362
378
 
363
- # Is used as a before_save callback to check while saving a collection
379
+ # Is used as an around_save callback to check while saving a collection
364
380
  # association whether or not the parent was a new record before saving.
365
- def before_save_collection_association
366
- @new_record_before_save = new_record?
367
- end
381
+ def around_save_collection_association
382
+ previously_new_record_before_save = (@new_record_before_save ||= false)
383
+ @new_record_before_save = !previously_new_record_before_save && new_record?
368
384
 
369
- def after_save_collection_association
370
- @new_record_before_save = false
385
+ yield
386
+ ensure
387
+ @new_record_before_save = previously_new_record_before_save
371
388
  end
372
389
 
373
390
  # Saves any new associated records, or all loaded autosave associations if
@@ -440,13 +457,13 @@ module ActiveRecord
440
457
  if autosave && record.marked_for_destruction?
441
458
  record.destroy
442
459
  elsif autosave != false
443
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
460
+ key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id
444
461
 
445
- if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
462
+ if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key)
446
463
  unless reflection.through_reflection
447
464
  record[reflection.foreign_key] = key
448
465
  if inverse_reflection = reflection.inverse_of
449
- record.association(inverse_reflection.name).loaded!
466
+ record.association(inverse_reflection.name).inversed_from(self)
450
467
  end
451
468
  end
452
469
 
@@ -468,7 +485,7 @@ module ActiveRecord
468
485
  def association_foreign_key_changed?(reflection, record, key)
469
486
  return false if reflection.through_reflection?
470
487
 
471
- record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
488
+ record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key
472
489
  end
473
490
 
474
491
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -489,7 +506,7 @@ module ActiveRecord
489
506
  saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
490
507
 
491
508
  if association.updated?
492
- association_id = record.send(reflection.options[:primary_key] || :id)
509
+ association_id = record.public_send(reflection.options[:primary_key] || :id)
493
510
  self[reflection.foreign_key] = association_id
494
511
  association.loaded!
495
512
  end
@@ -499,10 +516,12 @@ module ActiveRecord
499
516
  end
500
517
  end
501
518
 
519
+ def custom_validation_context?
520
+ validation_context && [:create, :update].exclude?(validation_context)
521
+ end
522
+
502
523
  def _ensure_no_duplicate_errors
503
- errors.messages.each_key do |attribute|
504
- errors[attribute].uniq!
505
- end
524
+ errors.uniq!
506
525
  end
507
526
  end
508
527
  end