activerecord 6.0.3.4 → 6.1.2

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

Potentially problematic release.


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

Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +891 -695
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  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 -1
  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 +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  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 +72 -50
  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 +11 -5
  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 -8
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +33 -8
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +152 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  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 +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -96
  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/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -58
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +218 -71
  104. data/lib/active_record/core.rb +245 -61
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +82 -38
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +58 -9
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +35 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +16 -7
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +33 -17
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +27 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +68 -17
  136. data/lib/active_record/model_schema.rb +117 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/console_sandbox.rb +2 -4
  144. data/lib/active_record/railties/databases.rake +276 -99
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +71 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +59 -38
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +333 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +8 -7
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +2 -8
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +42 -51
  177. data/lib/active_record/tasks/database_tasks.rb +140 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +19 -66
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +25 -26
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
- attribute_method_suffix "_before_type_cast"
32
+ attribute_method_suffix "_before_type_cast", "_for_database"
33
33
  attribute_method_suffix "_came_from_user?"
34
34
  end
35
35
 
@@ -46,8 +46,10 @@ module ActiveRecord
46
46
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
47
47
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
48
48
  def read_attribute_before_type_cast(attr_name)
49
- sync_with_transaction_state if @transaction_state&.finalized?
50
- @attributes[attr_name.to_s].value_before_type_cast
49
+ name = attr_name.to_s
50
+ name = self.class.attribute_aliases[name] || name
51
+
52
+ attribute_before_type_cast(name)
51
53
  end
52
54
 
53
55
  # Returns a hash of attributes before typecasting and deserialization.
@@ -61,19 +63,21 @@ module ActiveRecord
61
63
  # task.attributes_before_type_cast
62
64
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
63
65
  def attributes_before_type_cast
64
- sync_with_transaction_state if @transaction_state&.finalized?
65
66
  @attributes.values_before_type_cast
66
67
  end
67
68
 
68
69
  private
69
70
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
70
- def attribute_before_type_cast(attribute_name)
71
- read_attribute_before_type_cast(attribute_name)
71
+ def attribute_before_type_cast(attr_name)
72
+ @attributes[attr_name].value_before_type_cast
73
+ end
74
+
75
+ def attribute_for_database(attr_name)
76
+ @attributes[attr_name].value_for_database
72
77
  end
73
78
 
74
- def attribute_came_from_user?(attribute_name)
75
- sync_with_transaction_state if @transaction_state&.finalized?
76
- @attributes[attribute_name].came_from_user?
79
+ def attribute_came_from_user?(attr_name)
80
+ @attributes[attr_name].came_from_user?
77
81
  end
78
82
  end
79
83
  end
@@ -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
  #
@@ -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,13 +44,17 @@ module ActiveRecord
44
44
  attribute_in_database(@primary_key)
45
45
  end
46
46
 
47
+ def id_for_database # :nodoc:
48
+ @attributes[@primary_key].value_for_database
49
+ end
50
+
47
51
  private
48
52
  def attribute_method?(attr_name)
49
53
  attr_name == "id" || super
50
54
  end
51
55
 
52
56
  module ClassMethods
53
- 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
54
58
 
55
59
  def instance_method_already_implemented?(method_name)
56
60
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -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,16 +7,14 @@ module ActiveRecord
7
7
 
8
8
  module ClassMethods # :nodoc:
9
9
  private
10
- def define_method_attribute(name)
10
+ def define_method_attribute(name, owner:)
11
11
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
- generated_attribute_methods, name
12
+ owner, name
13
13
  ) do |temp_method_name, attr_name_expr|
14
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
15
- def #{temp_method_name}
16
- name = #{attr_name_expr}
17
- _read_attribute(name) { |n| missing_attribute(n, caller) }
18
- end
19
- RUBY
14
+ owner <<
15
+ "def #{temp_method_name}" <<
16
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
+ "end"
20
18
  end
21
19
  end
22
20
  end
@@ -29,14 +27,13 @@ module ActiveRecord
29
27
  name = self.class.attribute_aliases[name] || name
30
28
 
31
29
  name = @primary_key if name == "id" && @primary_key
32
- _read_attribute(name, &block)
30
+ @attributes.fetch_value(name, &block)
33
31
  end
34
32
 
35
33
  # This method exists to avoid the expensive primary_key check internally, without
36
34
  # breaking compatibility with the read_attribute API
37
35
  def _read_attribute(attr_name, &block) # :nodoc
38
- sync_with_transaction_state if @transaction_state&.finalized?
39
- @attributes.fetch_value(attr_name.to_s, &block)
36
+ @attributes.fetch_value(attr_name, &block)
40
37
  end
41
38
 
42
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,12 +75,12 @@ 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
 
@@ -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
@@ -62,21 +68,14 @@ module ActiveRecord
62
68
  end
63
69
 
64
70
  module ClassMethods # :nodoc:
65
- private
66
- def inherited(subclass)
67
- super
68
- # We need to apply this decorator here, rather than on module inclusion. The closure
69
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
70
- # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
71
- # `skip_time_zone_conversion_for_attributes` would not be picked up.
72
- subclass.class_eval do
73
- matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
74
- decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
75
- TimeZoneConverter.new(type)
76
- end
77
- 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)
78
74
  end
75
+ super
76
+ end
79
77
 
78
+ private
80
79
  def create_time_zone_conversion_attribute?(name, cast_type)
81
80
  enabled_for_column = time_zone_aware_attributes &&
82
81
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -11,16 +11,14 @@ module ActiveRecord
11
11
 
12
12
  module ClassMethods # :nodoc:
13
13
  private
14
- def define_method_attribute=(name)
14
+ def define_method_attribute=(name, owner:)
15
15
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
16
- generated_attribute_methods, name, writer: true,
16
+ owner, name, writer: true,
17
17
  ) do |temp_method_name, attr_name_expr|
18
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
19
- def #{temp_method_name}(value)
20
- name = #{attr_name_expr}
21
- _write_attribute(name, value)
22
- end
23
- RUBY
18
+ owner <<
19
+ "def #{temp_method_name}(value)" <<
20
+ " _write_attribute(#{attr_name_expr}, value)" <<
21
+ "end"
24
22
  end
25
23
  end
26
24
  end
@@ -33,27 +31,21 @@ module ActiveRecord
33
31
  name = self.class.attribute_aliases[name] || name
34
32
 
35
33
  name = @primary_key if name == "id" && @primary_key
36
- _write_attribute(name, value)
34
+ @attributes.write_from_user(name, value)
37
35
  end
38
36
 
39
37
  # This method exists to avoid the expensive primary_key check internally, without
40
38
  # breaking compatibility with the write_attribute API
41
39
  def _write_attribute(attr_name, value) # :nodoc:
42
- sync_with_transaction_state if @transaction_state&.finalized?
43
- @attributes.write_from_user(attr_name.to_s, value)
44
- value
40
+ @attributes.write_from_user(attr_name, value)
45
41
  end
46
42
 
43
+ alias :attribute= :_write_attribute
44
+ private :attribute=
45
+
47
46
  private
48
47
  def write_attribute_without_type_cast(attr_name, value)
49
- sync_with_transaction_state if @transaction_state&.finalized?
50
- @attributes.write_cast_value(attr_name.to_s, value)
51
- value
52
- end
53
-
54
- # Dispatch target for <tt>*=</tt> attribute methods.
55
- def attribute=(attribute_name, value)
56
- _write_attribute(attribute_name, value)
48
+ @attributes.write_cast_value(attr_name, value)
57
49
  end
58
50
  end
59
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,11 +249,7 @@ 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
 
@@ -273,6 +272,32 @@ module ActiveRecord
273
272
  end
274
273
  _default_attributes[name] = default_attribute
275
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
276
301
  end
277
302
  end
278
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,8 +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
171
+
150
172
  def define_non_cyclic_method(name, &block)
151
- return if instance_methods(false).include?(name)
173
+ return if method_defined?(name, false)
174
+
152
175
  define_method(name) do |*args|
153
176
  result = true; @_already_called ||= {}
154
177
  # Loop prevention for validation of associations
@@ -180,8 +203,7 @@ module ActiveRecord
180
203
  save_method = :"autosave_associated_records_for_#{reflection.name}"
181
204
 
182
205
  if reflection.collection?
183
- before_save :before_save_collection_association
184
- after_save :after_save_collection_association
206
+ around_save :around_save_collection_association
185
207
 
186
208
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
187
209
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
@@ -279,8 +301,9 @@ module ActiveRecord
279
301
  end
280
302
  end
281
303
 
282
- # go through nested autosave associations that are loaded in memory (without loading
283
- # 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.
284
307
  def nested_records_changed_for_autosave?
285
308
  @_nested_records_changed_for_autosave_already_called ||= false
286
309
  return false if @_nested_records_changed_for_autosave_already_called
@@ -328,21 +351,16 @@ module ActiveRecord
328
351
  if reflection.options[:autosave]
329
352
  indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
330
353
 
331
- record.errors.each do |attribute, message|
354
+ record.errors.group_by_attribute.each { |attribute, errors|
332
355
  attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
333
- errors[attribute] << message
334
- errors[attribute].uniq!
335
- end
336
356
 
337
- record.errors.details.each_key do |attribute|
338
- reflection_attribute =
339
- normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
340
-
341
- record.errors.details[attribute].each do |error|
342
- errors.details[reflection_attribute] << error
343
- errors.details[reflection_attribute].uniq!
344
- end
345
- end
357
+ errors.each { |error|
358
+ self.errors.import(
359
+ error,
360
+ attribute: attribute
361
+ )
362
+ }
363
+ }
346
364
  else
347
365
  errors.add(reflection.name)
348
366
  end
@@ -358,14 +376,15 @@ module ActiveRecord
358
376
  end
359
377
  end
360
378
 
361
- # 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
362
380
  # association whether or not the parent was a new record before saving.
363
- def before_save_collection_association
364
- @new_record_before_save ||= new_record?
365
- 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?
366
384
 
367
- def after_save_collection_association
368
- @new_record_before_save = false
385
+ yield
386
+ ensure
387
+ @new_record_before_save = previously_new_record_before_save
369
388
  end
370
389
 
371
390
  # Saves any new associated records, or all loaded autosave associations if
@@ -438,13 +457,13 @@ module ActiveRecord
438
457
  if autosave && record.marked_for_destruction?
439
458
  record.destroy
440
459
  elsif autosave != false
441
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
460
+ key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id
442
461
 
443
- 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)
444
463
  unless reflection.through_reflection
445
464
  record[reflection.foreign_key] = key
446
465
  if inverse_reflection = reflection.inverse_of
447
- record.association(inverse_reflection.name).loaded!
466
+ record.association(inverse_reflection.name).inversed_from(self)
448
467
  end
449
468
  end
450
469
 
@@ -466,7 +485,7 @@ module ActiveRecord
466
485
  def association_foreign_key_changed?(reflection, record, key)
467
486
  return false if reflection.through_reflection?
468
487
 
469
- 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
470
489
  end
471
490
 
472
491
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -487,7 +506,7 @@ module ActiveRecord
487
506
  saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
488
507
 
489
508
  if association.updated?
490
- association_id = record.send(reflection.options[:primary_key] || :id)
509
+ association_id = record.public_send(reflection.options[:primary_key] || :id)
491
510
  self[reflection.foreign_key] = association_id
492
511
  association.loaded!
493
512
  end
@@ -502,9 +521,7 @@ module ActiveRecord
502
521
  end
503
522
 
504
523
  def _ensure_no_duplicate_errors
505
- errors.messages.each_key do |attribute|
506
- errors[attribute].uniq!
507
- end
524
+ errors.uniq!
508
525
  end
509
526
  end
510
527
  end