activerecord 6.1.3.2 → 7.0.0.alpha2

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -1058
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +35 -7
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +16 -6
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +24 -25
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +11 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  67. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  82. data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
  83. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  87. data/lib/active_record/connection_adapters.rb +8 -5
  88. data/lib/active_record/connection_handling.rb +20 -38
  89. data/lib/active_record/core.rb +129 -117
  90. data/lib/active_record/database_configurations/database_config.rb +12 -0
  91. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  92. data/lib/active_record/database_configurations/url_config.rb +2 -2
  93. data/lib/active_record/database_configurations.rb +18 -9
  94. data/lib/active_record/delegated_type.rb +33 -11
  95. data/lib/active_record/destroy_association_async_job.rb +1 -1
  96. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  97. data/lib/active_record/dynamic_matchers.rb +1 -1
  98. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  99. data/lib/active_record/encryption/cipher.rb +53 -0
  100. data/lib/active_record/encryption/config.rb +44 -0
  101. data/lib/active_record/encryption/configurable.rb +61 -0
  102. data/lib/active_record/encryption/context.rb +35 -0
  103. data/lib/active_record/encryption/contexts.rb +72 -0
  104. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  105. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  106. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  107. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  108. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  109. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  110. data/lib/active_record/encryption/encryptor.rb +155 -0
  111. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  112. data/lib/active_record/encryption/errors.rb +15 -0
  113. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  114. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  115. data/lib/active_record/encryption/key.rb +28 -0
  116. data/lib/active_record/encryption/key_generator.rb +42 -0
  117. data/lib/active_record/encryption/key_provider.rb +46 -0
  118. data/lib/active_record/encryption/message.rb +33 -0
  119. data/lib/active_record/encryption/message_serializer.rb +80 -0
  120. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  121. data/lib/active_record/encryption/properties.rb +76 -0
  122. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  123. data/lib/active_record/encryption/scheme.rb +99 -0
  124. data/lib/active_record/encryption.rb +55 -0
  125. data/lib/active_record/enum.rb +44 -46
  126. data/lib/active_record/errors.rb +66 -3
  127. data/lib/active_record/fixture_set/file.rb +15 -1
  128. data/lib/active_record/fixture_set/table_row.rb +40 -5
  129. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  130. data/lib/active_record/fixtures.rb +16 -11
  131. data/lib/active_record/future_result.rb +139 -0
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +55 -17
  134. data/lib/active_record/insert_all.rb +39 -6
  135. data/lib/active_record/integration.rb +1 -1
  136. data/lib/active_record/internal_metadata.rb +3 -5
  137. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  138. data/lib/active_record/locking/optimistic.rb +10 -9
  139. data/lib/active_record/log_subscriber.rb +6 -2
  140. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  141. data/lib/active_record/middleware/database_selector.rb +8 -3
  142. data/lib/active_record/migration/command_recorder.rb +4 -4
  143. data/lib/active_record/migration/compatibility.rb +83 -1
  144. data/lib/active_record/migration/join_table.rb +1 -1
  145. data/lib/active_record/migration.rb +109 -79
  146. data/lib/active_record/model_schema.rb +46 -32
  147. data/lib/active_record/nested_attributes.rb +3 -3
  148. data/lib/active_record/no_touching.rb +2 -2
  149. data/lib/active_record/null_relation.rb +2 -6
  150. data/lib/active_record/persistence.rb +134 -45
  151. data/lib/active_record/query_cache.rb +2 -2
  152. data/lib/active_record/query_logs.rb +203 -0
  153. data/lib/active_record/querying.rb +15 -5
  154. data/lib/active_record/railtie.rb +117 -17
  155. data/lib/active_record/railties/controller_runtime.rb +1 -1
  156. data/lib/active_record/railties/databases.rake +83 -58
  157. data/lib/active_record/readonly_attributes.rb +11 -0
  158. data/lib/active_record/reflection.rb +45 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  160. data/lib/active_record/relation/batches.rb +3 -3
  161. data/lib/active_record/relation/calculations.rb +42 -25
  162. data/lib/active_record/relation/delegation.rb +6 -6
  163. data/lib/active_record/relation/finder_methods.rb +32 -23
  164. data/lib/active_record/relation/merger.rb +20 -13
  165. data/lib/active_record/relation/predicate_builder.rb +1 -6
  166. data/lib/active_record/relation/query_attribute.rb +5 -11
  167. data/lib/active_record/relation/query_methods.rb +233 -50
  168. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  169. data/lib/active_record/relation/spawn_methods.rb +2 -2
  170. data/lib/active_record/relation/where_clause.rb +22 -15
  171. data/lib/active_record/relation.rb +170 -87
  172. data/lib/active_record/result.rb +17 -2
  173. data/lib/active_record/runtime_registry.rb +2 -4
  174. data/lib/active_record/sanitization.rb +11 -7
  175. data/lib/active_record/schema_dumper.rb +3 -3
  176. data/lib/active_record/schema_migration.rb +0 -4
  177. data/lib/active_record/scoping/default.rb +62 -15
  178. data/lib/active_record/scoping/named.rb +3 -11
  179. data/lib/active_record/scoping.rb +40 -22
  180. data/lib/active_record/serialization.rb +1 -1
  181. data/lib/active_record/signed_id.rb +1 -1
  182. data/lib/active_record/statement_cache.rb +2 -2
  183. data/lib/active_record/tasks/database_tasks.rb +107 -23
  184. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  185. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  186. data/lib/active_record/test_databases.rb +1 -1
  187. data/lib/active_record/test_fixtures.rb +45 -4
  188. data/lib/active_record/timestamp.rb +3 -4
  189. data/lib/active_record/transactions.rb +9 -14
  190. data/lib/active_record/translation.rb +2 -2
  191. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  192. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  193. data/lib/active_record/type/internal/timezone.rb +2 -2
  194. data/lib/active_record/type/serialized.rb +1 -1
  195. data/lib/active_record/type/type_map.rb +17 -20
  196. data/lib/active_record/type.rb +1 -2
  197. data/lib/active_record/validations/associated.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +1 -1
  199. data/lib/active_record.rb +170 -2
  200. data/lib/arel/attributes/attribute.rb +0 -8
  201. data/lib/arel/collectors/bind.rb +2 -2
  202. data/lib/arel/collectors/composite.rb +3 -3
  203. data/lib/arel/collectors/sql_string.rb +1 -1
  204. data/lib/arel/collectors/substitute_binds.rb +1 -1
  205. data/lib/arel/crud.rb +18 -22
  206. data/lib/arel/delete_manager.rb +2 -4
  207. data/lib/arel/insert_manager.rb +2 -3
  208. data/lib/arel/nodes/casted.rb +1 -1
  209. data/lib/arel/nodes/delete_statement.rb +8 -13
  210. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  211. data/lib/arel/nodes/insert_statement.rb +2 -2
  212. data/lib/arel/nodes/select_core.rb +2 -2
  213. data/lib/arel/nodes/select_statement.rb +2 -2
  214. data/lib/arel/nodes/update_statement.rb +3 -2
  215. data/lib/arel/predications.rb +3 -3
  216. data/lib/arel/select_manager.rb +10 -4
  217. data/lib/arel/table.rb +0 -1
  218. data/lib/arel/tree_manager.rb +0 -12
  219. data/lib/arel/update_manager.rb +2 -4
  220. data/lib/arel/visitors/dot.rb +80 -90
  221. data/lib/arel/visitors/mysql.rb +6 -1
  222. data/lib/arel/visitors/postgresql.rb +0 -10
  223. data/lib/arel/visitors/to_sql.rb +44 -3
  224. data/lib/arel.rb +1 -1
  225. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  227. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  228. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  229. metadata +55 -16
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  def execute_callstack_for_multiparameter_attributes(callstack)
47
47
  errors = []
48
48
  callstack.each do |name, values_with_empty_parameters|
49
- if values_with_empty_parameters.each_value.all?(&:nil?)
49
+ if values_with_empty_parameters.each_value.all?(NilClass)
50
50
  values = nil
51
51
  else
52
52
  values = values_with_empty_parameters
@@ -29,8 +29,8 @@ module ActiveRecord
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
- attribute_method_suffix "_before_type_cast", "_for_database"
33
- attribute_method_suffix "_came_from_user?"
32
+ attribute_method_suffix "_before_type_cast", "_for_database", parameters: false
33
+ attribute_method_suffix "_came_from_user?", parameters: false
34
34
  end
35
35
 
36
36
  # Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +66,11 @@ module ActiveRecord
66
66
  @attributes.values_before_type_cast
67
67
  end
68
68
 
69
+ # Returns a hash of attributes for assignment to the database.
70
+ def attributes_for_database
71
+ @attributes.values_for_database
72
+ end
73
+
69
74
  private
70
75
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
71
76
  def attribute_before_type_cast(attr_name)
@@ -14,16 +14,43 @@ module ActiveRecord
14
14
  raise "You cannot include Dirty after Timestamp"
15
15
  end
16
16
 
17
- class_attribute :partial_writes, instance_writer: false, default: true
17
+ class_attribute :partial_updates, instance_writer: false, default: true
18
+ class_attribute :partial_inserts, instance_writer: false, default: true
18
19
 
19
20
  # Attribute methods for "changed in last call to save?"
20
- attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
21
- attribute_method_prefix("saved_change_to_")
22
- attribute_method_suffix("_before_last_save")
21
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?", parameters: "**options")
22
+ attribute_method_prefix("saved_change_to_", parameters: false)
23
+ attribute_method_suffix("_before_last_save", parameters: false)
23
24
 
24
25
  # Attribute methods for "will change if I call save?"
25
- attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
26
- attribute_method_suffix("_change_to_be_saved", "_in_database")
26
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?", parameters: "**options")
27
+ attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
28
+ end
29
+
30
+ module ClassMethods
31
+ def partial_writes
32
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
33
+ ActiveRecord::Base.partial_writes is deprecated and will be removed in Rails 7.1.
34
+ Use `partial_updates` and `partial_inserts` instead.
35
+ MSG
36
+ partial_updates && partial_inserts
37
+ end
38
+
39
+ def partial_writes?
40
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
41
+ `ActiveRecord::Base.partial_writes?` is deprecated and will be removed in Rails 7.1.
42
+ Use `partial_updates?` and `partial_inserts?` instead.
43
+ MSG
44
+ partial_updates? && partial_inserts?
45
+ end
46
+
47
+ def partial_writes=(value)
48
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
49
+ `ActiveRecord::Base.partial_writes=` is deprecated and will be removed in Rails 7.1.
50
+ Use `partial_updates=` and `partial_inserts=` instead.
51
+ MSG
52
+ self.partial_updates = self.partial_inserts = value
53
+ end
27
54
  end
28
55
 
29
56
  # <tt>reload</tt> the record and clears changed attributes.
@@ -156,12 +183,6 @@ module ActiveRecord
156
183
  end
157
184
 
158
185
  private
159
- def write_attribute_without_type_cast(attr_name, value)
160
- result = super
161
- clear_attribute_change(attr_name)
162
- result
163
- end
164
-
165
186
  def _touch_row(attribute_names, time)
166
187
  @_touch_attr_names = Set.new(attribute_names)
167
188
 
@@ -191,20 +212,24 @@ module ActiveRecord
191
212
  @_touch_attr_names, @_skip_dirty_tracking = nil, nil
192
213
  end
193
214
 
194
- def _update_record(attribute_names = attribute_names_for_partial_writes)
215
+ def _update_record(attribute_names = attribute_names_for_partial_updates)
195
216
  affected_rows = super
196
217
  changes_applied
197
218
  affected_rows
198
219
  end
199
220
 
200
- def _create_record(attribute_names = attribute_names_for_partial_writes)
221
+ def _create_record(attribute_names = attribute_names_for_partial_inserts)
201
222
  id = super
202
223
  changes_applied
203
224
  id
204
225
  end
205
226
 
206
- def attribute_names_for_partial_writes
207
- partial_writes? ? changed_attribute_names_to_save : attribute_names
227
+ def attribute_names_for_partial_updates
228
+ partial_updates? ? changed_attribute_names_to_save : attribute_names
229
+ end
230
+
231
+ def attribute_names_for_partial_inserts
232
+ partial_inserts? ? changed_attribute_names_to_save : attribute_names
208
233
  end
209
234
  end
210
235
  end
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
79
79
  end
80
80
 
81
- def reset_primary_key #:nodoc:
81
+ def reset_primary_key # :nodoc:
82
82
  if base_class?
83
83
  self.primary_key = get_primary_key(base_class.name)
84
84
  else
@@ -86,7 +86,7 @@ module ActiveRecord
86
86
  end
87
87
  end
88
88
 
89
- def get_primary_key(base_name) #:nodoc:
89
+ def get_primary_key(base_name) # :nodoc:
90
90
  if base_name && primary_key_prefix_type == :table_name
91
91
  base_name.foreign_key(false)
92
92
  elsif base_name && primary_key_prefix_type == :table_name_with_underscore
@@ -6,11 +6,11 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- attribute_method_suffix "?"
9
+ attribute_method_suffix "?", parameters: false
10
10
  end
11
11
 
12
12
  def query_attribute(attr_name)
13
- value = self[attr_name]
13
+ value = self.public_send(attr_name)
14
14
 
15
15
  case value
16
16
  when true then true
@@ -11,10 +11,12 @@ module ActiveRecord
11
11
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
12
  owner, name
13
13
  ) do |temp_method_name, attr_name_expr|
14
- owner <<
15
- "def #{temp_method_name}" <<
16
- " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
- "end"
14
+ owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ batch <<
16
+ "def #{temp_method_name}" <<
17
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
18
+ "end"
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
 
33
35
  # This method exists to avoid the expensive primary_key check internally, without
34
36
  # breaking compatibility with the read_attribute API
35
- def _read_attribute(attr_name, &block) # :nodoc
37
+ def _read_attribute(attr_name, &block) # :nodoc:
36
38
  @attributes.fetch_value(attr_name, &block)
37
39
  end
38
40
 
@@ -16,15 +16,45 @@ module ActiveRecord
16
16
  end
17
17
 
18
18
  module ClassMethods
19
- # If you have an attribute that needs to be saved to the database as an
20
- # object, and retrieved as the same object, then specify the name of that
21
- # attribute using this method and it will be handled automatically. The
22
- # serialization is done through YAML. If +class_name+ is specified, the
23
- # serialized object must be of that class on assignment and retrieval.
24
- # Otherwise SerializationTypeMismatch will be raised.
19
+ # If you have an attribute that needs to be saved to the database as a
20
+ # serialized object, and retrieved by deserializing into the same object,
21
+ # then specify the name of that attribute using this method and serialization
22
+ # will be handled automatically.
25
23
  #
26
- # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
27
- # +Array+, will always be persisted as null.
24
+ # The serialization format may be YAML, JSON, or any custom format using a
25
+ # custom coder class.
26
+ #
27
+ # === Serialization formats
28
+ #
29
+ # serialize attr_name [, class_name_or_coder]
30
+ #
31
+ # | | database storage |
32
+ # class_name_or_coder | attribute read/write type | serialized | NULL |
33
+ # ---------------------+---------------------------+------------+--------+
34
+ # <not given> | any value that supports | YAML | |
35
+ # | .to_yaml | | |
36
+ # | | | |
37
+ # Array | Array ** | YAML | [] |
38
+ # | | | |
39
+ # Hash | Hash ** | YAML | {} |
40
+ # | | | |
41
+ # JSON | any value that supports | JSON | |
42
+ # | .to_json | | |
43
+ # | | | |
44
+ # <custom coder class> | any value supported by | custom | custom |
45
+ # | the custom coder class | | |
46
+ #
47
+ # ** If +class_name_or_coder+ is +Array+ or +Hash+, values retrieved will
48
+ # always be of that type, and any value assigned must be of that type or
49
+ # +SerializationTypeMismatch+ will be raised.
50
+ #
51
+ # ==== Custom coders
52
+ # A custom coder class or module may be given. This must have +self.load+
53
+ # and +self.dump+ class/module methods. <tt>self.dump(object)</tt> will be called
54
+ # to serialize an object and should return the serialized value to be
55
+ # stored in the database (+nil+ to store as +NULL+). <tt>self.load(string)</tt>
56
+ # will be called to reverse the process and load (unserialize) from the
57
+ # database.
28
58
  #
29
59
  # Keep in mind that database adapters handle certain serialization tasks
30
60
  # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
@@ -38,8 +68,9 @@ module ActiveRecord
38
68
  # ==== Parameters
39
69
  #
40
70
  # * +attr_name+ - The field name that should be serialized.
41
- # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
42
- # or a class name that the object type should be equal to.
71
+ # * +class_name_or_coder+ - Optional, may be be +Array+ or +Hash+ or
72
+ # +JSON+ or a custom coder class or module which responds to +.load+
73
+ # and +.dump+. See table above.
43
74
  #
44
75
  # ==== Options
45
76
  #
@@ -49,7 +80,7 @@ module ActiveRecord
49
80
  #
50
81
  # ==== Example
51
82
  #
52
- # # Serialize a preferences attribute.
83
+ # # Serialize a preferences attribute using YAML coder.
53
84
  # class User < ActiveRecord::Base
54
85
  # serialize :preferences
55
86
  # end
@@ -63,6 +94,28 @@ module ActiveRecord
63
94
  # class User < ActiveRecord::Base
64
95
  # serialize :preferences, Hash
65
96
  # end
97
+ #
98
+ # # Serialize preferences using a custom coder.
99
+ # class Rot13JSON
100
+ # def self.rot13(string)
101
+ # string.tr("a-zA-Z", "n-za-mN-ZA-M")
102
+ # end
103
+ #
104
+ # # returns serialized string that will be stored in the database
105
+ # def self.dump(object)
106
+ # ActiveSupport::JSON.encode(object).rot13
107
+ # end
108
+ #
109
+ # # reverses the above, turning the serialized string from the database
110
+ # # back into its original value
111
+ # def self.load(string)
112
+ # ActiveSupport::JSON.decode(string.rot13)
113
+ # end
114
+ # end
115
+ #
116
+ # class User < ActiveRecord::Base
117
+ # serialize :preferences, Rot13JSON
118
+ # end
66
119
  def serialize(attr_name, class_name_or_coder = Object, **options)
67
120
  # When ::JSON is used, force it to go through the Active Support JSON encoder
68
121
  # to ensure special objects (e.g. Active Record models) are dumped correctly
@@ -75,11 +128,12 @@ module ActiveRecord
75
128
  Coders::YAMLColumn.new(attr_name, class_name_or_coder)
76
129
  end
77
130
 
78
- decorate_attribute_type(attr_name.to_s, **options) do |cast_type|
131
+ attribute(attr_name, **options) do |cast_type|
79
132
  if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
80
133
  raise ColumnNotSerializableError.new(attr_name, cast_type)
81
134
  end
82
135
 
136
+ cast_type = cast_type.subtype if Type::Serialized === cast_type
83
137
  Type::Serialized.new(cast_type, coder)
84
138
  end
85
139
  end
@@ -25,6 +25,8 @@ module ActiveRecord
25
25
  rescue ArgumentError
26
26
  nil
27
27
  end
28
+ elsif value.respond_to?(:infinite?) && value.infinite?
29
+ value
28
30
  else
29
31
  map_avoiding_infinite_recursion(super) { |v| cast(v) }
30
32
  end
@@ -36,7 +38,7 @@ module ActiveRecord
36
38
 
37
39
  if value.acts_like?(:time)
38
40
  value.in_time_zone
39
- elsif value.is_a?(::Float)
41
+ elsif value.respond_to?(:infinite?) && value.infinite?
40
42
  value
41
43
  else
42
44
  map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
@@ -61,8 +63,7 @@ module ActiveRecord
61
63
  extend ActiveSupport::Concern
62
64
 
63
65
  included do
64
- mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
65
-
66
+ class_attribute :time_zone_aware_attributes, instance_writer: false, default: false
66
67
  class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
67
68
  class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
68
69
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- attribute_method_suffix "="
9
+ attribute_method_suffix "=", parameters: "value"
10
10
  end
11
11
 
12
12
  module ClassMethods # :nodoc:
@@ -15,10 +15,12 @@ module ActiveRecord
15
15
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
16
16
  owner, name, writer: true,
17
17
  ) do |temp_method_name, attr_name_expr|
18
- owner <<
19
- "def #{temp_method_name}(value)" <<
20
- " _write_attribute(#{attr_name_expr}, value)" <<
21
- "end"
18
+ owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_record) do |batch|
19
+ batch <<
20
+ "def #{temp_method_name}(value)" <<
21
+ " _write_attribute(#{attr_name_expr}, value)" <<
22
+ "end"
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -42,11 +44,6 @@ module ActiveRecord
42
44
 
43
45
  alias :attribute= :_write_attribute
44
46
  private :attribute=
45
-
46
- private
47
- def write_attribute_without_type_cast(attr_name, value)
48
- @attributes.write_cast_value(attr_name, value)
49
- end
50
47
  end
51
48
  end
52
49
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
25
25
 
26
- class GeneratedAttributeMethods < Module #:nodoc:
26
+ class GeneratedAttributeMethods < Module # :nodoc:
27
27
  include Mutex_m
28
28
  end
29
29
 
@@ -39,7 +39,7 @@ module ActiveRecord
39
39
  end
40
40
 
41
41
  module ClassMethods
42
- def inherited(child_class) #:nodoc:
42
+ def inherited(child_class) # :nodoc:
43
43
  child_class.initialize_generated_modules
44
44
  super
45
45
  end
@@ -267,9 +267,8 @@ module ActiveRecord
267
267
 
268
268
  # Returns an <tt>#inspect</tt>-like string for the value of the
269
269
  # attribute +attr_name+. String attributes are truncated up to 50
270
- # characters, Date and Time attributes are returned in the
271
- # <tt>:db</tt> format. Other attributes return the value of
272
- # <tt>#inspect</tt> without modification.
270
+ # characters. Other attributes return the value of <tt>#inspect</tt>
271
+ # without modification.
273
272
  #
274
273
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
275
274
  #
@@ -277,7 +276,7 @@ module ActiveRecord
277
276
  # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
278
277
  #
279
278
  # person.attribute_for_inspect(:created_at)
280
- # # => "\"2012-10-22 00:15:07\""
279
+ # # => "\"2012-10-22 00:15:07.000000000 +0000\""
281
280
  #
282
281
  # person.attribute_for_inspect(:tag_ids)
283
282
  # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
@@ -385,9 +384,7 @@ module ActiveRecord
385
384
  end
386
385
 
387
386
  def attributes_with_values(attribute_names)
388
- attribute_names.index_with do |name|
389
- _read_attribute(name)
390
- end
387
+ attribute_names.index_with { |name| @attributes[name] }
391
388
  end
392
389
 
393
390
  # Filters the primary keys and readonly attributes from the attribute names.
@@ -12,9 +12,6 @@ module ActiveRecord
12
12
  end
13
13
 
14
14
  module ClassMethods
15
- ##
16
- # :call-seq: attribute(name, cast_type = nil, **options)
17
- #
18
15
  # Defines an attribute with a type on this model. It will override the
19
16
  # type of existing attributes if needed. This allows control over how
20
17
  # values are converted to and from SQL when assigned to a model. It also
@@ -208,14 +205,31 @@ module ActiveRecord
208
205
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
209
206
  # will be called from ActiveModel::Dirty. See the documentation for those
210
207
  # methods in ActiveModel::Type::Value for more details.
211
- def attribute(name, cast_type = nil, **options, &block)
208
+ def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
212
209
  name = name.to_s
210
+ name = attribute_aliases[name] || name
211
+
213
212
  reload_schema_from_cache
214
213
 
214
+ case cast_type
215
+ when Symbol
216
+ cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
217
+ when nil
218
+ if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
219
+ default = prev_default if default == NO_DEFAULT_PROVIDED
220
+ else
221
+ prev_cast_type = -> subtype { subtype }
222
+ end
223
+
224
+ cast_type = if block_given?
225
+ -> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
226
+ else
227
+ prev_cast_type
228
+ end
229
+ end
230
+
215
231
  self.attributes_to_define_after_schema_loads =
216
- attributes_to_define_after_schema_loads.merge(
217
- name => [cast_type || block, options]
218
- )
232
+ attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
219
233
  end
220
234
 
221
235
  # This is the low level API which sits beneath +attribute+. It only
@@ -248,8 +262,9 @@ module ActiveRecord
248
262
 
249
263
  def load_schema! # :nodoc:
250
264
  super
251
- attributes_to_define_after_schema_loads.each do |name, (type, options)|
252
- define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
265
+ attributes_to_define_after_schema_loads.each do |name, (cast_type, default)|
266
+ cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
267
+ define_attribute(name, cast_type, default: default)
253
268
  end
254
269
  end
255
270
 
@@ -272,32 +287,6 @@ module ActiveRecord
272
287
  end
273
288
  _default_attributes[name] = default_attribute
274
289
  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
301
290
  end
302
291
  end
303
292
  end