activerecord 7.0.8.1 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_model/forbidden_attributes_protection"
4
-
5
3
  module ActiveRecord
6
4
  module AttributeAssignment
7
- include ActiveModel::AttributeAssignment
8
-
9
5
  private
10
6
  def _assign_attributes(attributes)
11
7
  multi_parameter_attributes = nested_parameter_attributes = nil
@@ -52,6 +52,23 @@ module ActiveRecord
52
52
  attribute_before_type_cast(name)
53
53
  end
54
54
 
55
+ # Returns the value of the attribute identified by +attr_name+ after
56
+ # serialization.
57
+ #
58
+ # class Book < ActiveRecord::Base
59
+ # enum :status, { draft: 1, published: 2 }
60
+ # end
61
+ #
62
+ # book = Book.new(status: "published")
63
+ # book.read_attribute(:status) # => "published"
64
+ # book.read_attribute_for_database(:status) # => 2
65
+ def read_attribute_for_database(attr_name)
66
+ name = attr_name.to_s
67
+ name = self.class.attribute_aliases[name] || name
68
+
69
+ attribute_for_database(name)
70
+ end
71
+
55
72
  # Returns a hash of attributes before typecasting and deserialization.
56
73
  #
57
74
  # class Task < ActiveRecord::Base
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module CompositePrimaryKey # :nodoc:
6
+ # Returns the primary key column's value. If the primary key is composite,
7
+ # returns an array of the primary key column values.
8
+ def id
9
+ if self.class.composite_primary_key?
10
+ @primary_key.map { |pk| _read_attribute(pk) }
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def primary_key_values_present? # :nodoc:
17
+ if self.class.composite_primary_key?
18
+ id.all?
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ # Sets the primary key column's value. If the primary key is composite,
25
+ # raises TypeError when the set value not enumerable.
26
+ def id=(value)
27
+ if self.class.composite_primary_key?
28
+ raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable)
29
+ @primary_key.zip(value) { |attr, value| _write_attribute(attr, value) }
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ # Queries the primary key column's value. If the primary key is composite,
36
+ # all primary key column values must be queryable.
37
+ def id?
38
+ if self.class.composite_primary_key?
39
+ @primary_key.all? { |col| _query_attribute(col) }
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ # Returns the primary key column's value before type cast. If the primary key is composite,
46
+ # returns an array of primary key column values before type cast.
47
+ def id_before_type_cast
48
+ if self.class.composite_primary_key?
49
+ @primary_key.map { |col| attribute_before_type_cast(col) }
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ # Returns the primary key column's previous value. If the primary key is composite,
56
+ # returns an array of primary key column previous values.
57
+ def id_was
58
+ if self.class.composite_primary_key?
59
+ @primary_key.map { |col| attribute_was(col) }
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ # Returns the primary key column's value from the database. If the primary key is composite,
66
+ # returns an array of primary key column values from database.
67
+ def id_in_database
68
+ if self.class.composite_primary_key?
69
+ @primary_key.map { |col| attribute_in_database(col) }
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ def id_for_database # :nodoc:
76
+ if self.class.composite_primary_key?
77
+ @primary_key.map { |col| @attributes[col].value_for_database }
78
+ else
79
+ super
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -4,6 +4,38 @@ require "active_support/core_ext/module/attribute_accessors"
4
4
 
5
5
  module ActiveRecord
6
6
  module AttributeMethods
7
+ # = Active Record Attribute Methods \Dirty
8
+ #
9
+ # Provides a way to track changes in your Active Record models. It adds all
10
+ # methods from ActiveModel::Dirty and adds database-specific methods.
11
+ #
12
+ # A newly created +Person+ object is unchanged:
13
+ #
14
+ # class Person < ActiveRecord::Base
15
+ # end
16
+ #
17
+ # person = Person.create(name: "Allison")
18
+ # person.changed? # => false
19
+ #
20
+ # Change the name:
21
+ #
22
+ # person.name = 'Alice'
23
+ # person.name_in_database # => "Allison"
24
+ # person.will_save_change_to_name? # => true
25
+ # person.name_change_to_be_saved # => ["Allison", "Alice"]
26
+ # person.changes_to_save # => {"name"=>["Allison", "Alice"]}
27
+ #
28
+ # Save the changes:
29
+ #
30
+ # person.save
31
+ # person.name_in_database # => "Alice"
32
+ # person.saved_change_to_name? # => true
33
+ # person.saved_change_to_name # => ["Allison", "Alice"]
34
+ # person.name_before_last_save # => "Allison"
35
+ #
36
+ # Similar to ActiveModel::Dirty, methods can be invoked as
37
+ # +saved_change_to_name?+ or by passing an argument to the generic method
38
+ # <tt>saved_change_to_attribute?("name")</tt>.
7
39
  module Dirty
8
40
  extend ActiveSupport::Concern
9
41
 
@@ -27,32 +59,6 @@ module ActiveRecord
27
59
  attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
28
60
  end
29
61
 
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
54
- end
55
-
56
62
  # <tt>reload</tt> the record and clears changed attributes.
57
63
  def reload(*)
58
64
  super.tap do
@@ -70,11 +76,13 @@ module ActiveRecord
70
76
  #
71
77
  # ==== Options
72
78
  #
73
- # +from+ When passed, this method will return false unless the original
74
- # value is equal to the given option
79
+ # [+from+]
80
+ # When specified, this method will return false unless the original
81
+ # value is equal to the given value.
75
82
  #
76
- # +to+ When passed, this method will return false unless the value was
77
- # changed to the given value
83
+ # [+to+]
84
+ # When specified, this method will return false unless the value will be
85
+ # changed to the given value.
78
86
  def saved_change_to_attribute?(attr_name, **options)
79
87
  mutations_before_last_save.changed?(attr_name.to_s, **options)
80
88
  end
@@ -120,11 +128,13 @@ module ActiveRecord
120
128
  #
121
129
  # ==== Options
122
130
  #
123
- # +from+ When passed, this method will return false unless the original
124
- # value is equal to the given option
131
+ # [+from+]
132
+ # When specified, this method will return false unless the original
133
+ # value is equal to the given value.
125
134
  #
126
- # +to+ When passed, this method will return false unless the value will be
127
- # changed to the given value
135
+ # [+to+]
136
+ # When specified, this method will return false unless the value will be
137
+ # changed to the given value.
128
138
  def will_save_change_to_attribute?(attr_name, **options)
129
139
  mutations_from_database.changed?(attr_name.to_s, **options)
130
140
  end
@@ -183,6 +193,14 @@ module ActiveRecord
183
193
  end
184
194
 
185
195
  private
196
+ def init_internals
197
+ super
198
+ @mutations_before_last_save = nil
199
+ @mutations_from_database = nil
200
+ @_touch_attr_names = nil
201
+ @_skip_dirty_tracking = nil
202
+ end
203
+
186
204
  def _touch_row(attribute_names, time)
187
205
  @_touch_attr_names = Set.new(attribute_names)
188
206
 
@@ -233,7 +251,7 @@ module ActiveRecord
233
251
  changed_attribute_names_to_save
234
252
  else
235
253
  attribute_names.reject do |attr_name|
236
- if column_for_attribute(attr_name).default_function
254
+ if column_for_attribute(attr_name).auto_populated?
237
255
  !attribute_changed?(attr_name)
238
256
  end
239
257
  end
@@ -4,6 +4,7 @@ require "set"
4
4
 
5
5
  module ActiveRecord
6
6
  module AttributeMethods
7
+ # = Active Record Attribute Methods Primary Key
7
8
  module PrimaryKey
8
9
  extend ActiveSupport::Concern
9
10
 
@@ -11,35 +12,45 @@ module ActiveRecord
11
12
  # available.
12
13
  def to_key
13
14
  key = id
14
- [key] if key
15
+ Array(key) if key
15
16
  end
16
17
 
17
- # Returns the primary key column's value.
18
+ # Returns the primary key column's value. If the primary key is composite,
19
+ # returns an array of the primary key column values.
18
20
  def id
19
21
  _read_attribute(@primary_key)
20
22
  end
21
23
 
22
- # Sets the primary key column's value.
24
+ def primary_key_values_present? # :nodoc:
25
+ !!id
26
+ end
27
+
28
+ # Sets the primary key column's value. If the primary key is composite,
29
+ # raises TypeError when the set value not enumerable.
23
30
  def id=(value)
24
31
  _write_attribute(@primary_key, value)
25
32
  end
26
33
 
27
- # Queries the primary key column's value.
34
+ # Queries the primary key column's value. If the primary key is composite,
35
+ # all primary key column values must be queryable.
28
36
  def id?
29
- query_attribute(@primary_key)
37
+ _query_attribute(@primary_key)
30
38
  end
31
39
 
32
- # Returns the primary key column's value before type cast.
40
+ # Returns the primary key column's value before type cast. If the primary key is composite,
41
+ # returns an array of primary key column values before type cast.
33
42
  def id_before_type_cast
34
43
  attribute_before_type_cast(@primary_key)
35
44
  end
36
45
 
37
- # Returns the primary key column's previous value.
46
+ # Returns the primary key column's previous value. If the primary key is composite,
47
+ # returns an array of primary key column previous values.
38
48
  def id_was
39
49
  attribute_was(@primary_key)
40
50
  end
41
51
 
42
- # Returns the primary key column's value from the database.
52
+ # Returns the primary key column's value from the database. If the primary key is composite,
53
+ # returns an array of primary key column values from database.
43
54
  def id_in_database
44
55
  attribute_in_database(@primary_key)
45
56
  end
@@ -55,6 +66,7 @@ module ActiveRecord
55
66
 
56
67
  module ClassMethods
57
68
  ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
69
+ PRIMARY_KEY_NOT_SET = BasicObject.new
58
70
 
59
71
  def instance_method_already_implemented?(method_name)
60
72
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -68,14 +80,19 @@ module ActiveRecord
68
80
  # Overwriting will negate any effect of the +primary_key_prefix_type+
69
81
  # setting, though.
70
82
  def primary_key
71
- @primary_key = reset_primary_key unless defined? @primary_key
83
+ reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
72
84
  @primary_key
73
85
  end
74
86
 
87
+ def composite_primary_key? # :nodoc:
88
+ reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
89
+ @composite_primary_key
90
+ end
91
+
75
92
  # Returns a quoted version of the primary key name, used to construct
76
93
  # SQL statements.
77
94
  def quoted_primary_key
78
- @quoted_primary_key ||= connection.quote_column_name(primary_key)
95
+ @quoted_primary_key ||= adapter_class.quote_column_name(primary_key)
79
96
  end
80
97
 
81
98
  def reset_primary_key # :nodoc:
@@ -91,13 +108,10 @@ module ActiveRecord
91
108
  base_name.foreign_key(false)
92
109
  elsif base_name && primary_key_prefix_type == :table_name_with_underscore
93
110
  base_name.foreign_key
111
+ elsif ActiveRecord::Base != self && table_exists?
112
+ schema_cache.primary_keys(table_name)
94
113
  else
95
- if ActiveRecord::Base != self && table_exists?
96
- pk = connection.schema_cache.primary_keys(table_name)
97
- suppress_composite_primary_key(pk)
98
- else
99
- "id"
100
- end
114
+ "id"
101
115
  end
102
116
  end
103
117
 
@@ -117,20 +131,26 @@ module ActiveRecord
117
131
  #
118
132
  # Project.primary_key # => "foo_id"
119
133
  def primary_key=(value)
120
- @primary_key = value && -value.to_s
134
+ @primary_key = if value.is_a?(Array)
135
+ @composite_primary_key = true
136
+ include CompositePrimaryKey
137
+ @primary_key = value.map { |v| -v.to_s }.freeze
138
+ elsif value
139
+ -value.to_s
140
+ end
121
141
  @quoted_primary_key = nil
122
142
  @attributes_builder = nil
123
143
  end
124
144
 
125
145
  private
126
- def suppress_composite_primary_key(pk)
127
- return pk unless pk.is_a?(Array)
128
-
129
- warn <<~WARNING
130
- WARNING: Active Record does not support composite primary key.
131
-
132
- #{table_name} has composite primary key. Composite primary key is ignored.
133
- WARNING
146
+ def inherited(base)
147
+ super
148
+ base.class_eval do
149
+ @primary_key = PRIMARY_KEY_NOT_SET
150
+ @composite_primary_key = false
151
+ @quoted_primary_key = nil
152
+ @attributes_builder = nil
153
+ end
134
154
  end
135
155
  end
136
156
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
+ # = Active Record Attribute Methods \Query
5
6
  module Query
6
7
  extend ActiveSupport::Concern
7
8
 
@@ -12,27 +13,38 @@ module ActiveRecord
12
13
  def query_attribute(attr_name)
13
14
  value = self.public_send(attr_name)
14
15
 
15
- case value
16
- when true then true
17
- when false, nil then false
18
- else
19
- if !type_for_attribute(attr_name) { false }
20
- if Numeric === value || !value.match?(/[^0-9]/)
21
- !value.to_i.zero?
16
+ query_cast_attribute(attr_name, value)
17
+ end
18
+
19
+ def _query_attribute(attr_name) # :nodoc:
20
+ value = self._read_attribute(attr_name.to_s)
21
+
22
+ query_cast_attribute(attr_name, value)
23
+ end
24
+
25
+ alias :attribute? :query_attribute
26
+ private :attribute?
27
+
28
+ private
29
+ def query_cast_attribute(attr_name, value)
30
+ case value
31
+ when true then true
32
+ when false, nil then false
33
+ else
34
+ if !type_for_attribute(attr_name) { false }
35
+ if Numeric === value || !value.match?(/[^0-9]/)
36
+ !value.to_i.zero?
37
+ else
38
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
39
+ !value.blank?
40
+ end
41
+ elsif value.respond_to?(:zero?)
42
+ !value.zero?
22
43
  else
23
- return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
24
44
  !value.blank?
25
45
  end
26
- elsif value.respond_to?(:zero?)
27
- !value.zero?
28
- else
29
- !value.blank?
30
46
  end
31
47
  end
32
- end
33
-
34
- alias :attribute? :query_attribute
35
- private :attribute?
36
48
  end
37
49
  end
38
50
  end
@@ -2,16 +2,17 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
+ # = Active Record Attribute Methods \Read
5
6
  module Read
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  module ClassMethods # :nodoc:
9
10
  private
10
- def define_method_attribute(name, owner:)
11
+ def define_method_attribute(canonical_name, owner:, as: canonical_name)
11
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
- owner, name
13
+ owner, canonical_name
13
14
  ) do |temp_method_name, attr_name_expr|
14
- owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
15
16
  batch <<
16
17
  "def #{temp_method_name}" <<
17
18
  " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
@@ -21,14 +22,14 @@ module ActiveRecord
21
22
  end
22
23
  end
23
24
 
24
- # Returns the value of the attribute identified by <tt>attr_name</tt> after
25
- # it has been typecast (for example, "2004-12-12" in a date column is cast
26
- # to a date object, like <tt>Date.new(2004, 12, 12)</tt>).
25
+ # Returns the value of the attribute identified by +attr_name+ after it
26
+ # has been type cast. For example, a date attribute will cast "2004-12-12"
27
+ # to <tt>Date.new(2004, 12, 12)</tt>. (For information about specific type
28
+ # casting behavior, see the types under ActiveModel::Type.)
27
29
  def read_attribute(attr_name, &block)
28
30
  name = attr_name.to_s
29
31
  name = self.class.attribute_aliases[name] || name
30
32
 
31
- name = @primary_key if name == "id" && @primary_key
32
33
  @attributes.fetch_value(name, &block)
33
34
  end
34
35