activerecord 7.0.0 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -1,7 +1,5 @@
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
5
  include ActiveModel::AttributeAssignment
@@ -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
@@ -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: "Alisson")
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_change # => "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
 
@@ -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,41 +12,80 @@ 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
- _read_attribute(@primary_key)
21
+ return _read_attribute(@primary_key) unless @primary_key.is_a?(Array)
22
+
23
+ @primary_key.map { |pk| _read_attribute(pk) }
24
+ end
25
+
26
+ def primary_key_values_present? # :nodoc:
27
+ return id.all? if self.class.composite_primary_key?
28
+
29
+ !!id
20
30
  end
21
31
 
22
- # Sets the primary key column's value.
32
+ # Sets the primary key column's value. If the primary key is composite,
33
+ # raises TypeError when the set value not enumerable.
23
34
  def id=(value)
24
- _write_attribute(@primary_key, value)
35
+ if self.class.composite_primary_key?
36
+ raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable)
37
+ @primary_key.zip(value) { |attr, value| _write_attribute(attr, value) }
38
+ else
39
+ _write_attribute(@primary_key, value)
40
+ end
25
41
  end
26
42
 
27
- # Queries the primary key column's value.
43
+ # Queries the primary key column's value. If the primary key is composite,
44
+ # all primary key column values must be queryable.
28
45
  def id?
29
- query_attribute(@primary_key)
46
+ if self.class.composite_primary_key?
47
+ @primary_key.all? { |col| _query_attribute(col) }
48
+ else
49
+ _query_attribute(@primary_key)
50
+ end
30
51
  end
31
52
 
32
- # Returns the primary key column's value before type cast.
53
+ # Returns the primary key column's value before type cast. If the primary key is composite,
54
+ # returns an array of primary key column values before type cast.
33
55
  def id_before_type_cast
34
- attribute_before_type_cast(@primary_key)
56
+ if self.class.composite_primary_key?
57
+ @primary_key.map { |col| attribute_before_type_cast(col) }
58
+ else
59
+ attribute_before_type_cast(@primary_key)
60
+ end
35
61
  end
36
62
 
37
- # Returns the primary key column's previous value.
63
+ # Returns the primary key column's previous value. If the primary key is composite,
64
+ # returns an array of primary key column previous values.
38
65
  def id_was
39
- attribute_was(@primary_key)
66
+ if self.class.composite_primary_key?
67
+ @primary_key.map { |col| attribute_was(col) }
68
+ else
69
+ attribute_was(@primary_key)
70
+ end
40
71
  end
41
72
 
42
- # Returns the primary key column's value from the database.
73
+ # Returns the primary key column's value from the database. If the primary key is composite,
74
+ # returns an array of primary key column values from database.
43
75
  def id_in_database
44
- attribute_in_database(@primary_key)
76
+ if self.class.composite_primary_key?
77
+ @primary_key.map { |col| attribute_in_database(col) }
78
+ else
79
+ attribute_in_database(@primary_key)
80
+ end
45
81
  end
46
82
 
47
83
  def id_for_database # :nodoc:
48
- @attributes[@primary_key].value_for_database
84
+ if self.class.composite_primary_key?
85
+ @primary_key.map { |col| @attributes[col].value_for_database }
86
+ else
87
+ @attributes[@primary_key].value_for_database
88
+ end
49
89
  end
50
90
 
51
91
  private
@@ -55,6 +95,7 @@ module ActiveRecord
55
95
 
56
96
  module ClassMethods
57
97
  ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
98
+ PRIMARY_KEY_NOT_SET = BasicObject.new
58
99
 
59
100
  def instance_method_already_implemented?(method_name)
60
101
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -68,10 +109,16 @@ module ActiveRecord
68
109
  # Overwriting will negate any effect of the +primary_key_prefix_type+
69
110
  # setting, though.
70
111
  def primary_key
71
- @primary_key = reset_primary_key unless defined? @primary_key
112
+ if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
113
+ @primary_key = reset_primary_key
114
+ end
72
115
  @primary_key
73
116
  end
74
117
 
118
+ def composite_primary_key? # :nodoc:
119
+ primary_key.is_a?(Array)
120
+ end
121
+
75
122
  # Returns a quoted version of the primary key name, used to construct
76
123
  # SQL statements.
77
124
  def quoted_primary_key
@@ -93,8 +140,7 @@ module ActiveRecord
93
140
  base_name.foreign_key
94
141
  else
95
142
  if ActiveRecord::Base != self && table_exists?
96
- pk = connection.schema_cache.primary_keys(table_name)
97
- suppress_composite_primary_key(pk)
143
+ connection.schema_cache.primary_keys(table_name)
98
144
  else
99
145
  "id"
100
146
  end
@@ -117,20 +163,26 @@ module ActiveRecord
117
163
  #
118
164
  # Project.primary_key # => "foo_id"
119
165
  def primary_key=(value)
120
- @primary_key = value && -value.to_s
166
+ @primary_key = derive_primary_key(value)
121
167
  @quoted_primary_key = nil
122
168
  @attributes_builder = nil
123
169
  end
124
170
 
125
171
  private
126
- def suppress_composite_primary_key(pk)
127
- return pk unless pk.is_a?(Array)
172
+ def derive_primary_key(value)
173
+ return unless value
174
+
175
+ return -value.to_s unless value.is_a?(Array)
128
176
 
129
- warn <<~WARNING
130
- WARNING: Active Record does not support composite primary key.
177
+ value.map { |v| -v.to_s }.freeze
178
+ end
131
179
 
132
- #{table_name} has composite primary key. Composite primary key is ignored.
133
- WARNING
180
+ def inherited(base)
181
+ super
182
+ base.class_eval do
183
+ @primary_key = PRIMARY_KEY_NOT_SET
184
+ @quoted_primary_key = nil
185
+ end
134
186
  end
135
187
  end
136
188
  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,6 +2,7 @@
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
 
@@ -21,15 +22,27 @@ 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 Date.new(2004, 12, 12)).
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
- @attributes.fetch_value(name, &block)
33
+ return @attributes.fetch_value(name, &block) unless name == "id" && @primary_key
34
+
35
+ if self.class.composite_primary_key?
36
+ @attributes.fetch_value("id", &block)
37
+ else
38
+ if @primary_key != "id"
39
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
40
+ Using read_attribute(:id) to read the primary key value is deprecated.
41
+ Use #id instead.
42
+ MSG
43
+ end
44
+ @attributes.fetch_value(@primary_key, &block)
45
+ end
33
46
  end
34
47
 
35
48
  # This method exists to avoid the expensive primary_key check internally, without