activerecord 6.1.7 → 7.1.5

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  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 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class AsynchronousQueriesTracker # :nodoc:
5
+ module NullSession # :nodoc:
6
+ class << self
7
+ def active?
8
+ true
9
+ end
10
+
11
+ def finalize
12
+ end
13
+ end
14
+ end
15
+
16
+ class Session # :nodoc:
17
+ def initialize
18
+ @active = true
19
+ end
20
+
21
+ def active?
22
+ @active
23
+ end
24
+
25
+ def finalize
26
+ @active = false
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def install_executor_hooks(executor = ActiveSupport::Executor)
32
+ executor.register_hook(self)
33
+ end
34
+
35
+ def run
36
+ ActiveRecord::Base.asynchronous_queries_tracker.start_session
37
+ end
38
+
39
+ def complete(asynchronous_queries_tracker)
40
+ asynchronous_queries_tracker.finalize_session
41
+ end
42
+ end
43
+
44
+ attr_reader :current_session
45
+
46
+ def initialize
47
+ @current_session = NullSession
48
+ end
49
+
50
+ def start_session
51
+ @current_session = Session.new
52
+ self
53
+ end
54
+
55
+ def finalize_session
56
+ @current_session.finalize
57
+ @current_session = NullSession
58
+ end
59
+ end
60
+ end
@@ -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
@@ -46,7 +44,7 @@ module ActiveRecord
46
44
  def execute_callstack_for_multiparameter_attributes(callstack)
47
45
  errors = []
48
46
  callstack.each do |name, values_with_empty_parameters|
49
- if values_with_empty_parameters.each_value.all?(&:nil?)
47
+ if values_with_empty_parameters.each_value.all?(NilClass)
50
48
  values = nil
51
49
  else
52
50
  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
@@ -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
@@ -66,6 +83,11 @@ module ActiveRecord
66
83
  @attributes.values_before_type_cast
67
84
  end
68
85
 
86
+ # Returns a hash of attributes for assignment to the database.
87
+ def attributes_for_database
88
+ @attributes.values_for_database
89
+ end
90
+
69
91
  private
70
92
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
71
93
  def attribute_before_type_cast(attr_name)
@@ -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
 
@@ -14,16 +46,17 @@ module ActiveRecord
14
46
  raise "You cannot include Dirty after Timestamp"
15
47
  end
16
48
 
17
- class_attribute :partial_writes, instance_writer: false, default: true
49
+ class_attribute :partial_updates, instance_writer: false, default: true
50
+ class_attribute :partial_inserts, instance_writer: false, default: true
18
51
 
19
52
  # 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")
53
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?", parameters: "**options")
54
+ attribute_method_prefix("saved_change_to_", parameters: false)
55
+ attribute_method_suffix("_before_last_save", parameters: false)
23
56
 
24
57
  # 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")
58
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?", parameters: "**options")
59
+ attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
27
60
  end
28
61
 
29
62
  # <tt>reload</tt> the record and clears changed attributes.
@@ -43,11 +76,13 @@ module ActiveRecord
43
76
  #
44
77
  # ==== Options
45
78
  #
46
- # +from+ When passed, this method will return false unless the original
47
- # 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.
48
82
  #
49
- # +to+ When passed, this method will return false unless the value was
50
- # 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.
51
86
  def saved_change_to_attribute?(attr_name, **options)
52
87
  mutations_before_last_save.changed?(attr_name.to_s, **options)
53
88
  end
@@ -93,11 +128,13 @@ module ActiveRecord
93
128
  #
94
129
  # ==== Options
95
130
  #
96
- # +from+ When passed, this method will return false unless the original
97
- # 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.
98
134
  #
99
- # +to+ When passed, this method will return false unless the value will be
100
- # 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.
101
138
  def will_save_change_to_attribute?(attr_name, **options)
102
139
  mutations_from_database.changed?(attr_name.to_s, **options)
103
140
  end
@@ -156,10 +193,12 @@ module ActiveRecord
156
193
  end
157
194
 
158
195
  private
159
- def write_attribute_without_type_cast(attr_name, value)
160
- result = super
161
- clear_attribute_change(attr_name)
162
- result
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
163
202
  end
164
203
 
165
204
  def _touch_row(attribute_names, time)
@@ -191,20 +230,32 @@ module ActiveRecord
191
230
  @_touch_attr_names, @_skip_dirty_tracking = nil, nil
192
231
  end
193
232
 
194
- def _update_record(attribute_names = attribute_names_for_partial_writes)
233
+ def _update_record(attribute_names = attribute_names_for_partial_updates)
195
234
  affected_rows = super
196
235
  changes_applied
197
236
  affected_rows
198
237
  end
199
238
 
200
- def _create_record(attribute_names = attribute_names_for_partial_writes)
239
+ def _create_record(attribute_names = attribute_names_for_partial_inserts)
201
240
  id = super
202
241
  changes_applied
203
242
  id
204
243
  end
205
244
 
206
- def attribute_names_for_partial_writes
207
- partial_writes? ? changed_attribute_names_to_save : attribute_names
245
+ def attribute_names_for_partial_updates
246
+ partial_updates? ? changed_attribute_names_to_save : attribute_names
247
+ end
248
+
249
+ def attribute_names_for_partial_inserts
250
+ if partial_inserts?
251
+ changed_attribute_names_to_save
252
+ else
253
+ attribute_names.reject do |attr_name|
254
+ if column_for_attribute(attr_name).auto_populated?
255
+ !attribute_changed?(attr_name)
256
+ end
257
+ end
258
+ end
208
259
  end
209
260
  end
210
261
  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,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,17 +109,23 @@ 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
78
125
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
79
126
  end
80
127
 
81
- def reset_primary_key #:nodoc:
128
+ def reset_primary_key # :nodoc:
82
129
  if base_class?
83
130
  self.primary_key = get_primary_key(base_class.name)
84
131
  else
@@ -86,15 +133,14 @@ module ActiveRecord
86
133
  end
87
134
  end
88
135
 
89
- def get_primary_key(base_name) #:nodoc:
136
+ def get_primary_key(base_name) # :nodoc:
90
137
  if base_name && primary_key_prefix_type == :table_name
91
138
  base_name.foreign_key(false)
92
139
  elsif base_name && primary_key_prefix_type == :table_name_with_underscore
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,37 +2,49 @@
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
 
8
9
  included do
9
- attribute_method_suffix "?"
10
+ attribute_method_suffix "?", parameters: false
10
11
  end
11
12
 
12
13
  def query_attribute(attr_name)
13
- value = self[attr_name]
14
-
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?
14
+ value = self.public_send(attr_name)
15
+
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,37 +2,52 @@
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 <<
15
- "def #{temp_method_name}" <<
16
- " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
- "end"
15
+ owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
16
+ batch <<
17
+ "def #{temp_method_name}" <<
18
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
19
+ "end"
20
+ end
18
21
  end
19
22
  end
20
23
  end
21
24
 
22
- # Returns the value of the attribute identified by <tt>attr_name</tt> after
23
- # it has been typecast (for example, "2004-12-12" in a date column is cast
24
- # 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.)
25
29
  def read_attribute(attr_name, &block)
26
30
  name = attr_name.to_s
27
31
  name = self.class.attribute_aliases[name] || name
28
32
 
29
- name = @primary_key if name == "id" && @primary_key
30
- @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
31
46
  end
32
47
 
33
48
  # This method exists to avoid the expensive primary_key check internally, without
34
49
  # breaking compatibility with the read_attribute API
35
- def _read_attribute(attr_name, &block) # :nodoc
50
+ def _read_attribute(attr_name, &block) # :nodoc:
36
51
  @attributes.fetch_value(attr_name, &block)
37
52
  end
38
53