activerecord 6.1.7 → 7.1.0

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 (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -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 +50 -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 +35 -31
  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.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. 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: "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
 
@@ -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.
@@ -156,10 +189,12 @@ module ActiveRecord
156
189
  end
157
190
 
158
191
  private
159
- def write_attribute_without_type_cast(attr_name, value)
160
- result = super
161
- clear_attribute_change(attr_name)
162
- result
192
+ def init_internals
193
+ super
194
+ @mutations_before_last_save = nil
195
+ @mutations_from_database = nil
196
+ @_touch_attr_names = nil
197
+ @_skip_dirty_tracking = nil
163
198
  end
164
199
 
165
200
  def _touch_row(attribute_names, time)
@@ -191,20 +226,32 @@ module ActiveRecord
191
226
  @_touch_attr_names, @_skip_dirty_tracking = nil, nil
192
227
  end
193
228
 
194
- def _update_record(attribute_names = attribute_names_for_partial_writes)
229
+ def _update_record(attribute_names = attribute_names_for_partial_updates)
195
230
  affected_rows = super
196
231
  changes_applied
197
232
  affected_rows
198
233
  end
199
234
 
200
- def _create_record(attribute_names = attribute_names_for_partial_writes)
235
+ def _create_record(attribute_names = attribute_names_for_partial_inserts)
201
236
  id = super
202
237
  changes_applied
203
238
  id
204
239
  end
205
240
 
206
- def attribute_names_for_partial_writes
207
- partial_writes? ? changed_attribute_names_to_save : attribute_names
241
+ def attribute_names_for_partial_updates
242
+ partial_updates? ? changed_attribute_names_to_save : attribute_names
243
+ end
244
+
245
+ def attribute_names_for_partial_inserts
246
+ if partial_inserts?
247
+ changed_attribute_names_to_save
248
+ else
249
+ attribute_names.reject do |attr_name|
250
+ if column_for_attribute(attr_name).default_function
251
+ !attribute_changed?(attr_name)
252
+ end
253
+ end
254
+ end
208
255
  end
209
256
  end
210
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,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,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
 
@@ -11,28 +12,42 @@ module ActiveRecord
11
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
13
  owner, 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(name, as: temp_method_name, 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