activerecord 7.0.8.7 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1795 -1424
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  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 +20 -4
  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 +19 -13
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  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 +319 -217
  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 +53 -35
  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 +21 -8
  33. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  35. data/lib/active_record/attribute_methods/write.rb +6 -6
  36. data/lib/active_record/attribute_methods.rb +145 -21
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +59 -10
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +10 -24
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  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 +22 -143
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -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 +6 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  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 +14 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  84. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  85. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  88. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  89. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  90. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  91. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  92. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  93. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  94. data/lib/active_record/connection_adapters.rb +3 -1
  95. data/lib/active_record/connection_handling.rb +72 -95
  96. data/lib/active_record/core.rb +181 -154
  97. data/lib/active_record/counter_cache.rb +52 -27
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +15 -10
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +3 -1
  106. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  108. data/lib/active_record/encryption/config.rb +25 -1
  109. data/lib/active_record/encryption/configurable.rb +12 -19
  110. data/lib/active_record/encryption/context.rb +10 -3
  111. data/lib/active_record/encryption/contexts.rb +5 -1
  112. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  113. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  114. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  115. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  116. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  117. data/lib/active_record/encryption/key_generator.rb +12 -1
  118. data/lib/active_record/encryption/message_serializer.rb +2 -0
  119. data/lib/active_record/encryption/properties.rb +3 -3
  120. data/lib/active_record/encryption/scheme.rb +22 -21
  121. data/lib/active_record/encryption.rb +3 -0
  122. data/lib/active_record/enum.rb +112 -28
  123. data/lib/active_record/errors.rb +112 -18
  124. data/lib/active_record/explain.rb +23 -3
  125. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  126. data/lib/active_record/fixture_set/render_context.rb +2 -0
  127. data/lib/active_record/fixture_set/table_row.rb +29 -8
  128. data/lib/active_record/fixtures.rb +135 -71
  129. data/lib/active_record/future_result.rb +40 -5
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +30 -16
  132. data/lib/active_record/insert_all.rb +57 -10
  133. data/lib/active_record/integration.rb +8 -8
  134. data/lib/active_record/internal_metadata.rb +120 -30
  135. data/lib/active_record/locking/optimistic.rb +1 -1
  136. data/lib/active_record/locking/pessimistic.rb +5 -2
  137. data/lib/active_record/log_subscriber.rb +29 -12
  138. data/lib/active_record/marshalling.rb +59 -0
  139. data/lib/active_record/message_pack.rb +124 -0
  140. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  141. data/lib/active_record/middleware/database_selector.rb +6 -8
  142. data/lib/active_record/middleware/shard_selector.rb +3 -1
  143. data/lib/active_record/migration/command_recorder.rb +104 -5
  144. data/lib/active_record/migration/compatibility.rb +145 -5
  145. data/lib/active_record/migration/default_strategy.rb +23 -0
  146. data/lib/active_record/migration/execution_strategy.rb +19 -0
  147. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  148. data/lib/active_record/migration.rb +219 -111
  149. data/lib/active_record/model_schema.rb +69 -44
  150. data/lib/active_record/nested_attributes.rb +37 -8
  151. data/lib/active_record/normalization.rb +167 -0
  152. data/lib/active_record/persistence.rb +188 -37
  153. data/lib/active_record/promise.rb +84 -0
  154. data/lib/active_record/query_cache.rb +4 -22
  155. data/lib/active_record/query_logs.rb +77 -52
  156. data/lib/active_record/query_logs_formatter.rb +41 -0
  157. data/lib/active_record/querying.rb +15 -2
  158. data/lib/active_record/railtie.rb +107 -45
  159. data/lib/active_record/railties/controller_runtime.rb +12 -6
  160. data/lib/active_record/railties/databases.rake +144 -150
  161. data/lib/active_record/railties/job_runtime.rb +23 -0
  162. data/lib/active_record/readonly_attributes.rb +32 -5
  163. data/lib/active_record/reflection.rb +181 -45
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  165. data/lib/active_record/relation/batches.rb +190 -61
  166. data/lib/active_record/relation/calculations.rb +187 -63
  167. data/lib/active_record/relation/delegation.rb +23 -9
  168. data/lib/active_record/relation/finder_methods.rb +77 -16
  169. data/lib/active_record/relation/merger.rb +2 -0
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  173. data/lib/active_record/relation/predicate_builder.rb +26 -14
  174. data/lib/active_record/relation/query_attribute.rb +2 -1
  175. data/lib/active_record/relation/query_methods.rb +371 -68
  176. data/lib/active_record/relation/spawn_methods.rb +18 -1
  177. data/lib/active_record/relation.rb +103 -37
  178. data/lib/active_record/result.rb +19 -5
  179. data/lib/active_record/runtime_registry.rb +24 -1
  180. data/lib/active_record/sanitization.rb +51 -11
  181. data/lib/active_record/schema.rb +2 -3
  182. data/lib/active_record/schema_dumper.rb +46 -7
  183. data/lib/active_record/schema_migration.rb +68 -33
  184. data/lib/active_record/scoping/default.rb +15 -5
  185. data/lib/active_record/scoping/named.rb +2 -2
  186. data/lib/active_record/scoping.rb +2 -1
  187. data/lib/active_record/secure_password.rb +60 -0
  188. data/lib/active_record/secure_token.rb +21 -3
  189. data/lib/active_record/signed_id.rb +7 -5
  190. data/lib/active_record/store.rb +8 -8
  191. data/lib/active_record/suppressor.rb +3 -1
  192. data/lib/active_record/table_metadata.rb +10 -1
  193. data/lib/active_record/tasks/database_tasks.rb +152 -108
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  197. data/lib/active_record/test_fixtures.rb +114 -96
  198. data/lib/active_record/timestamp.rb +30 -16
  199. data/lib/active_record/token_for.rb +113 -0
  200. data/lib/active_record/touch_later.rb +11 -6
  201. data/lib/active_record/transactions.rb +36 -10
  202. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  203. data/lib/active_record/type/internal/timezone.rb +7 -2
  204. data/lib/active_record/type/time.rb +4 -0
  205. data/lib/active_record/validations/absence.rb +1 -1
  206. data/lib/active_record/validations/numericality.rb +5 -4
  207. data/lib/active_record/validations/presence.rb +5 -28
  208. data/lib/active_record/validations/uniqueness.rb +47 -2
  209. data/lib/active_record/validations.rb +8 -4
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/active_record.rb +122 -17
  212. data/lib/arel/errors.rb +10 -0
  213. data/lib/arel/factory_methods.rb +4 -0
  214. data/lib/arel/nodes/binary.rb +6 -1
  215. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  216. data/lib/arel/nodes/cte.rb +36 -0
  217. data/lib/arel/nodes/fragments.rb +35 -0
  218. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  219. data/lib/arel/nodes/leading_join.rb +8 -0
  220. data/lib/arel/nodes/node.rb +111 -2
  221. data/lib/arel/nodes/sql_literal.rb +6 -0
  222. data/lib/arel/nodes/table_alias.rb +4 -0
  223. data/lib/arel/nodes.rb +4 -0
  224. data/lib/arel/predications.rb +2 -0
  225. data/lib/arel/table.rb +9 -5
  226. data/lib/arel/tree_manager.rb +5 -1
  227. data/lib/arel/visitors/mysql.rb +8 -1
  228. data/lib/arel/visitors/to_sql.rb +83 -18
  229. data/lib/arel/visitors/visitor.rb +2 -2
  230. data/lib/arel.rb +16 -2
  231. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  232. data/lib/rails/generators/active_record/migration.rb +3 -1
  233. data/lib/rails/generators/active_record/model/USAGE +113 -0
  234. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  235. metadata +46 -10
  236. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  237. data/lib/active_record/null_relation.rb +0 -63
@@ -5,6 +5,10 @@ module ActiveRecord
5
5
  module CounterCache
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ included do
9
+ class_attribute :_counter_cache_columns, instance_accessor: false, default: []
10
+ end
11
+
8
12
  module ClassMethods
9
13
  # Resets one or more counter caches to their correct value using an SQL
10
14
  # count query. This is useful when adding new counter caches, or if the
@@ -29,6 +33,7 @@ module ActiveRecord
29
33
  def reset_counters(id, *counters, touch: nil)
30
34
  object = find(id)
31
35
 
36
+ updates = {}
32
37
  counters.each do |counter_association|
33
38
  has_many_association = _reflect_on_association(counter_association)
34
39
  unless has_many_association
@@ -47,19 +52,21 @@ module ActiveRecord
47
52
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
48
53
  counter_name = reflection.counter_cache_column
49
54
 
50
- updates = { counter_name => object.send(counter_association).count(:all) }
51
-
52
- if touch
53
- names = touch if touch != true
54
- names = Array.wrap(names)
55
- options = names.extract_options!
56
- touch_updates = touch_attributes_with_time(*names, **options)
57
- updates.merge!(touch_updates)
58
- end
55
+ count_was = object.send(counter_name)
56
+ count = object.send(counter_association).count(:all)
57
+ updates[counter_name] = count if count != count_was
58
+ end
59
59
 
60
- unscoped.where(primary_key => object.id).update_all(updates)
60
+ if touch
61
+ names = touch if touch != true
62
+ names = Array.wrap(names)
63
+ options = names.extract_options!
64
+ touch_updates = touch_attributes_with_time(*names, **options)
65
+ updates.merge!(touch_updates)
61
66
  end
62
67
 
68
+ unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
69
+
63
70
  true
64
71
  end
65
72
 
@@ -80,31 +87,32 @@ module ActiveRecord
80
87
  #
81
88
  # ==== Examples
82
89
  #
83
- # # For the Post with id of 5, decrement the comment_count by 1, and
84
- # # increment the action_count by 1
85
- # Post.update_counters 5, comment_count: -1, action_count: 1
90
+ # # For the Post with id of 5, decrement the comments_count by 1, and
91
+ # # increment the actions_count by 1
92
+ # Post.update_counters 5, comments_count: -1, actions_count: 1
86
93
  # # Executes the following SQL:
87
94
  # # UPDATE posts
88
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
89
- # # action_count = COALESCE(action_count, 0) + 1
95
+ # # SET comments_count = COALESCE(comments_count, 0) - 1,
96
+ # # actions_count = COALESCE(actions_count, 0) + 1
90
97
  # # WHERE id = 5
91
98
  #
92
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
93
- # Post.update_counters [10, 15], comment_count: 1
99
+ # # For the Posts with id of 10 and 15, increment the comments_count by 1
100
+ # Post.update_counters [10, 15], comments_count: 1
94
101
  # # Executes the following SQL:
95
102
  # # UPDATE posts
96
- # # SET comment_count = COALESCE(comment_count, 0) + 1
103
+ # # SET comments_count = COALESCE(comments_count, 0) + 1
97
104
  # # WHERE id IN (10, 15)
98
105
  #
99
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
106
+ # # For the Posts with id of 10 and 15, increment the comments_count by 1
100
107
  # # and update the updated_at value for each counter.
101
- # Post.update_counters [10, 15], comment_count: 1, touch: true
108
+ # Post.update_counters [10, 15], comments_count: 1, touch: true
102
109
  # # Executes the following SQL:
103
110
  # # UPDATE posts
104
- # # SET comment_count = COALESCE(comment_count, 0) + 1,
111
+ # # SET comments_count = COALESCE(comments_count, 0) + 1,
105
112
  # # `updated_at` = '2016-10-13T09:59:23-05:00'
106
113
  # # WHERE id IN (10, 15)
107
114
  def update_counters(id, counters)
115
+ id = [id] if composite_primary_key? && id.is_a?(Array) && !id[0].is_a?(Array)
108
116
  unscoped.where!(primary_key => id).update_counters(counters)
109
117
  end
110
118
 
@@ -119,6 +127,7 @@ module ActiveRecord
119
127
  #
120
128
  # * +counter_name+ - The name of the field that should be incremented.
121
129
  # * +id+ - The id of the object that should be incremented or an array of ids.
130
+ # * <tt>:by</tt> - The amount by which to increment the value. Defaults to +1+.
122
131
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
123
132
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
124
133
  # touch that column or an array of symbols to touch just those ones.
@@ -129,10 +138,14 @@ module ActiveRecord
129
138
  # DiscussionBoard.increment_counter(:posts_count, 5)
130
139
  #
131
140
  # # Increment the posts_count column for the record with an id of 5
141
+ # # by a specific amount.
142
+ # DiscussionBoard.increment_counter(:posts_count, 5, by: 3)
143
+ #
144
+ # # Increment the posts_count column for the record with an id of 5
132
145
  # # and update the updated_at value.
133
146
  # DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
134
- def increment_counter(counter_name, id, touch: nil)
135
- update_counters(id, counter_name => 1, touch: touch)
147
+ def increment_counter(counter_name, id, by: 1, touch: nil)
148
+ update_counters(id, counter_name => by, touch: touch)
136
149
  end
137
150
 
138
151
  # Decrement a numeric field by one, via a direct SQL update.
@@ -144,6 +157,7 @@ module ActiveRecord
144
157
  #
145
158
  # * +counter_name+ - The name of the field that should be decremented.
146
159
  # * +id+ - The id of the object that should be decremented or an array of ids.
160
+ # * <tt>:by</tt> - The amount by which to decrement the value. Defaults to +1+.
147
161
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
148
162
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
149
163
  # touch that column or an array of symbols to touch just those ones.
@@ -154,10 +168,18 @@ module ActiveRecord
154
168
  # DiscussionBoard.decrement_counter(:posts_count, 5)
155
169
  #
156
170
  # # Decrement the posts_count column for the record with an id of 5
171
+ # by a specific amount.
172
+ # DiscussionBoard.decrement_counter(:posts_count, 5, by: 3)
173
+ #
174
+ # # Decrement the posts_count column for the record with an id of 5
157
175
  # # and update the updated_at value.
158
176
  # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
159
- def decrement_counter(counter_name, id, touch: nil)
160
- update_counters(id, counter_name => -1, touch: touch)
177
+ def decrement_counter(counter_name, id, by: 1, touch: nil)
178
+ update_counters(id, counter_name => -by, touch: touch)
179
+ end
180
+
181
+ def counter_cache_column?(name) # :nodoc:
182
+ _counter_cache_columns.include?(name)
161
183
  end
162
184
  end
163
185
 
@@ -177,8 +199,7 @@ module ActiveRecord
177
199
 
178
200
  if affected_rows > 0
179
201
  each_counter_cached_associations do |association|
180
- foreign_key = association.reflection.foreign_key.to_sym
181
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
202
+ unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
182
203
  association.decrement_counters
183
204
  end
184
205
  end
@@ -192,5 +213,9 @@ module ActiveRecord
192
213
  yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
193
214
  end
194
215
  end
216
+
217
+ def _foreign_keys_equal?(fkey1, fkey2)
218
+ fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
219
+ end
195
220
  end
196
221
  end
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  attr_reader :uri
47
47
 
48
48
  def uri_parser
49
- @uri_parser ||= URI::Parser.new
49
+ @uri_parser ||= URI::RFC2396_Parser.new
50
50
  end
51
51
 
52
52
  # Converts the query parameters of the URI into a hash.
@@ -3,13 +3,11 @@
3
3
  module ActiveRecord
4
4
  class DatabaseConfigurations
5
5
  # ActiveRecord::Base.configurations will return either a HashConfig or
6
- # UrlConfig respectively. It will never return a DatabaseConfig object,
6
+ # UrlConfig respectively. It will never return a +DatabaseConfig+ object,
7
7
  # as this is the parent class for the types of database configuration objects.
8
8
  class DatabaseConfig # :nodoc:
9
9
  attr_reader :env_name, :name
10
10
 
11
- attr_accessor :owner_name
12
-
13
11
  def initialize(env_name, name)
14
12
  @env_name = env_name
15
13
  @name = name
@@ -19,6 +17,10 @@ module ActiveRecord
19
17
  "#{adapter}_connection"
20
18
  end
21
19
 
20
+ def adapter_class_method
21
+ "#{adapter}_adapter_class"
22
+ end
23
+
22
24
  def host
23
25
  raise NotImplementedError
24
26
  end
@@ -51,6 +53,10 @@ module ActiveRecord
51
53
  raise NotImplementedError
52
54
  end
53
55
 
56
+ def query_cache
57
+ raise NotImplementedError
58
+ end
59
+
54
60
  def checkout_timeout
55
61
  raise NotImplementedError
56
62
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class DatabaseConfigurations
5
- # A HashConfig object is created for each database configuration entry that
5
+ # = Active Record Database Hash Config
6
+ #
7
+ # A +HashConfig+ object is created for each database configuration entry that
6
8
  # is created from a hash.
7
9
  #
8
10
  # A hash config:
@@ -14,19 +16,23 @@ module ActiveRecord
14
16
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
15
17
  # @env_name="development", @name="primary", @config={database: "db_name"}>
16
18
  #
17
- # ==== Options
18
- #
19
- # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
20
- # * <tt>:name</tt> - The db config name. In a standard two-tier
21
- # database configuration this will default to "primary". In a multiple
22
- # database three-tier database configuration this corresponds to the name
23
- # used in the second tier, for example "primary_readonly".
24
- # * <tt>:config</tt> - The config hash. This is the hash that contains the
25
- # database adapter, name, and other important information for database
26
- # connections.
19
+ # See ActiveRecord::DatabaseConfigurations for more info.
27
20
  class HashConfig < DatabaseConfig
28
21
  attr_reader :configuration_hash
29
22
 
23
+
24
+ # Initialize a new +HashConfig+ object
25
+ #
26
+ # ==== Options
27
+ #
28
+ # * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
29
+ # * <tt>:name</tt> - The db config name. In a standard two-tier
30
+ # database configuration this will default to "primary". In a multiple
31
+ # database three-tier database configuration this corresponds to the name
32
+ # used in the second tier, for example "primary_readonly".
33
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
34
+ # database adapter, name, and other important information for database
35
+ # connections.
30
36
  def initialize(env_name, name, configuration_hash)
31
37
  super(env_name, name)
32
38
  @configuration_hash = configuration_hash.symbolize_keys.freeze
@@ -74,6 +80,10 @@ module ActiveRecord
74
80
  (configuration_hash[:max_threads] || pool).to_i
75
81
  end
76
82
 
83
+ def query_cache
84
+ configuration_hash[:query_cache]
85
+ end
86
+
77
87
  def max_queue
78
88
  max_threads * 4
79
89
  end
@@ -104,8 +114,12 @@ module ActiveRecord
104
114
  configuration_hash[:schema_cache_path]
105
115
  end
106
116
 
107
- def default_schema_cache_path
108
- "db/schema_cache.yml"
117
+ def default_schema_cache_path(db_dir = "db")
118
+ if primary?
119
+ File.join(db_dir, "schema_cache.yml")
120
+ else
121
+ File.join(db_dir, "#{name}_schema_cache.yml")
122
+ end
109
123
  end
110
124
 
111
125
  def lazy_schema_cache_path
@@ -122,7 +136,7 @@ module ActiveRecord
122
136
  # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
123
137
  # the schema will not be dumped.
124
138
  #
125
- # If the config option is set that will be used. Otherwise Rails
139
+ # If the config option is set that will be used. Otherwise \Rails
126
140
  # will generate the filename from the database config name.
127
141
  def schema_dump(format = ActiveRecord.schema_format)
128
142
  if configuration_hash.key?(:schema_dump)
@@ -2,7 +2,9 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class DatabaseConfigurations
5
- # A UrlConfig object is created for each database configuration
5
+ # = Active Record Database Url Config
6
+ #
7
+ # A +UrlConfig+ object is created for each database configuration
6
8
  # entry that is created from a URL. This can either be a URL string
7
9
  # or a hash with a URL in place of the config hash.
8
10
  #
@@ -17,20 +19,24 @@ module ActiveRecord
17
19
  # @config={adapter: "postgresql", database: "foo", host: "localhost"},
18
20
  # @url="postgres://localhost/foo">
19
21
  #
20
- # ==== Options
22
+ # See ActiveRecord::DatabaseConfigurations for more info.
21
23
  #
22
- # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
23
- # * <tt>:name</tt> - The db config name. In a standard two-tier
24
- # database configuration this will default to "primary". In a multiple
25
- # database three-tier database configuration this corresponds to the name
26
- # used in the second tier, for example "primary_readonly".
27
- # * <tt>:url</tt> - The database URL.
28
- # * <tt>:config</tt> - The config hash. This is the hash that contains the
29
- # database adapter, name, and other important information for database
30
- # connections.
31
24
  class UrlConfig < HashConfig
32
25
  attr_reader :url
33
26
 
27
+ # Initialize a new +UrlConfig+ object
28
+ #
29
+ # ==== Options
30
+ #
31
+ # * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
32
+ # * <tt>:name</tt> - The db config name. In a standard two-tier
33
+ # database configuration this will default to "primary". In a multiple
34
+ # database three-tier database configuration this corresponds to the name
35
+ # used in the second tier, for example "primary_readonly".
36
+ # * <tt>:url</tt> - The database URL.
37
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
38
+ # database adapter, name, and other important information for database
39
+ # connections.
34
40
  def initialize(env_name, name, url, configuration_hash = {})
35
41
  super(env_name, name, configuration_hash)
36
42
 
@@ -7,15 +7,69 @@ require "active_record/database_configurations/url_config"
7
7
  require "active_record/database_configurations/connection_url_resolver"
8
8
 
9
9
  module ActiveRecord
10
- # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
11
- # objects (either a HashConfig or UrlConfig) that are constructed from the
12
- # application's database configuration hash or URL string.
10
+ # = Active Record Database Configurations
11
+ #
12
+ # +ActiveRecord::DatabaseConfigurations+ returns an array of +DatabaseConfig+
13
+ # objects that are constructed from the application's database
14
+ # configuration hash or URL string.
15
+ #
16
+ # The array of +DatabaseConfig+ objects in an application default to either a
17
+ # HashConfig or UrlConfig. You can retrieve your application's config by using
18
+ # ActiveRecord::Base.configurations.
19
+ #
20
+ # If you register a custom handler, objects will be created according to the
21
+ # conditions of the handler. See ::register_db_config_handler for more on
22
+ # registering custom handlers.
13
23
  class DatabaseConfigurations
14
24
  class InvalidConfigurationError < StandardError; end
15
25
 
16
26
  attr_reader :configurations
17
27
  delegate :any?, to: :configurations
18
28
 
29
+ singleton_class.attr_accessor :db_config_handlers # :nodoc:
30
+ self.db_config_handlers = [] # :nodoc:
31
+
32
+ # Allows an application to register a custom handler for database configuration
33
+ # objects. This is useful for creating a custom handler that responds to
34
+ # methods your application needs but Active Record doesn't implement. For
35
+ # example if you are using Vitess, you may want your Vitess configurations
36
+ # to respond to `sharded?`. To implement this define the following in an
37
+ # initializer:
38
+ #
39
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
+ # next unless config.key?(:vitess)
41
+ # VitessConfig.new(env_name, name, config)
42
+ # end
43
+ #
44
+ # Note: applications must handle the condition in which custom config should be
45
+ # created in your handler registration otherwise all objects will use the custom
46
+ # handler.
47
+ #
48
+ # Then define your +VitessConfig+ to respond to the methods your application
49
+ # needs. It is recommended that you inherit from one of the existing
50
+ # database config classes to avoid having to reimplement all methods. Custom
51
+ # config handlers should only implement methods Active Record does not.
52
+ #
53
+ # class VitessConfig < ActiveRecord::DatabaseConfigurations::UrlConfig
54
+ # def sharded?
55
+ # configuration_hash.fetch("sharded", false)
56
+ # end
57
+ # end
58
+ #
59
+ # For configs that have a +:vitess+ key, a +VitessConfig+ object will be
60
+ # created instead of a +UrlConfig+.
61
+ def self.register_db_config_handler(&block)
62
+ db_config_handlers << block
63
+ end
64
+
65
+ register_db_config_handler do |env_name, name, url, config|
66
+ if url
67
+ UrlConfig.new(env_name, name, url, config)
68
+ else
69
+ HashConfig.new(env_name, name, config)
70
+ end
71
+ end
72
+
19
73
  def initialize(configurations = {})
20
74
  @configurations = build_configs(configurations)
21
75
  end
@@ -23,8 +77,8 @@ module ActiveRecord
23
77
  # Collects the configs for the environment and optionally the specification
24
78
  # name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
25
79
  #
26
- # If a name is provided a single DatabaseConfig object will be
27
- # returned, otherwise an array of DatabaseConfig objects will be
80
+ # If a name is provided a single +DatabaseConfig+ object will be
81
+ # returned, otherwise an array of +DatabaseConfig+ objects will be
28
82
  # returned that corresponds with the environment and type requested.
29
83
  #
30
84
  # ==== Options
@@ -34,20 +88,14 @@ module ActiveRecord
34
88
  # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
35
89
  # to +nil+. If no +env_name+ is specified the config for the default env and the
36
90
  # passed +name+ will be returned.
37
- # * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
38
- # the returned list. Most of the time we're only iterating over the write
39
- # connection (i.e. migrations don't need to run for the write and read connection).
40
- # Defaults to +false+.
91
+ # * <tt>config_key:</tt> Selects configs that contain a particular key in the configuration
92
+ # hash. Useful for selecting configs that use a custom db config handler or finding
93
+ # configs with hashes that contain a particular key.
41
94
  # * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
42
- # hidden by +database_tasks: false+ in the returned list. Most of the time we're only
95
+ # hidden by <tt>database_tasks: false</tt> in the returned list. Most of the time we're only
43
96
  # iterating over the primary connections (i.e. migrations don't need to run for the
44
97
  # write and read connection). Defaults to +false+.
45
- def configs_for(env_name: nil, name: nil, include_replicas: false, include_hidden: false)
46
- if include_replicas
47
- include_hidden = include_replicas
48
- ActiveSupport::Deprecation.warn("The kwarg `include_replicas` is deprecated in favor of `include_hidden`. When `include_hidden` is passed, configurations with `replica: true` or `database_tasks: false` will be returned. `include_replicas` will be removed in Rails 7.1.")
49
- end
50
-
98
+ def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false)
51
99
  env_name ||= default_env if name
52
100
  configs = env_with_configs(env_name)
53
101
 
@@ -57,6 +105,12 @@ module ActiveRecord
57
105
  end
58
106
  end
59
107
 
108
+ if config_key
109
+ configs = configs.select do |db_config|
110
+ db_config.configuration_hash.key?(config_key)
111
+ end
112
+ end
113
+
60
114
  if name
61
115
  configs.find do |db_config|
62
116
  db_config.name == name
@@ -66,17 +120,17 @@ module ActiveRecord
66
120
  end
67
121
  end
68
122
 
69
- # Returns a single DatabaseConfig object based on the requested environment.
123
+ # Returns a single +DatabaseConfig+ object based on the requested environment.
70
124
  #
71
125
  # If the application has multiple databases +find_db_config+ will return
72
- # the first DatabaseConfig for the environment.
126
+ # the first +DatabaseConfig+ for the environment.
73
127
  def find_db_config(env)
74
- configurations
75
- .sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] }
76
- .find do |db_config|
77
- db_config.env_name == env.to_s ||
78
- (db_config.for_current_env? && db_config.name == env.to_s)
79
- end
128
+ env = env.to_s
129
+ configurations.find do |db_config|
130
+ db_config.for_current_env? && (db_config.env_name == env || db_config.name == env)
131
+ end || configurations.find do |db_config|
132
+ db_config.env_name == env
133
+ end
80
134
  end
81
135
 
82
136
  # A primary configuration is one that is named primary or if there is
@@ -93,8 +147,6 @@ module ActiveRecord
93
147
  end
94
148
 
95
149
  # Checks if the application's configurations are empty.
96
- #
97
- # Aliased to blank?
98
150
  def empty?
99
151
  configurations.empty?
100
152
  end
@@ -219,15 +271,16 @@ module ActiveRecord
219
271
  end
220
272
 
221
273
  def build_db_config_from_hash(env_name, name, config)
222
- if config.has_key?(:url)
223
- url = config[:url]
224
- config_without_url = config.dup
225
- config_without_url.delete :url
274
+ url = config[:url]
275
+ config_without_url = config.dup
276
+ config_without_url.delete :url
226
277
 
227
- UrlConfig.new(env_name, name, url, config_without_url)
228
- else
229
- HashConfig.new(env_name, name, config)
278
+ DatabaseConfigurations.db_config_handlers.reverse_each do |handler|
279
+ config = handler.call(env_name, name, url, config_without_url)
280
+ return config if config
230
281
  end
282
+
283
+ nil
231
284
  end
232
285
 
233
286
  def merge_db_environment_variables(current_env, configs)
@@ -3,7 +3,7 @@
3
3
  require "active_support/core_ext/string/inquiry"
4
4
 
5
5
  module ActiveRecord
6
- # == Delegated types
6
+ # = Delegated types
7
7
  #
8
8
  # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
9
9
  # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  #
37
37
  # Let's look at that entry/message/comment example using delegated types:
38
38
  #
39
- # # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
39
+ # # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
40
40
  # class Entry < ApplicationRecord
41
41
  # belongs_to :account
42
42
  # belongs_to :creator
@@ -51,12 +51,12 @@ module ActiveRecord
51
51
  # end
52
52
  # end
53
53
  #
54
- # # Schema: messages[ id, subject, body ]
54
+ # # Schema: messages[ id, subject, body, created_at, updated_at ]
55
55
  # class Message < ApplicationRecord
56
56
  # include Entryable
57
57
  # end
58
58
  #
59
- # # Schema: comments[ id, content ]
59
+ # # Schema: comments[ id, content, created_at, updated_at ]
60
60
  # class Comment < ApplicationRecord
61
61
  # include Entryable
62
62
  # end
@@ -102,14 +102,14 @@ module ActiveRecord
102
102
  # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
103
103
  # like so:
104
104
  #
105
- # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
105
+ # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
106
106
  #
107
107
  # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
108
108
  # method or class to take care of the complicated needs. This could be as simple as:
109
109
  #
110
110
  # class Entry < ApplicationRecord
111
- # def self.create_with_comment(content, creator: Current.user)
112
- # create! entryable: Comment.new(content: content), creator: creator
111
+ # def self.create_with_comment(content, creator: Current.user, account: Current.account)
112
+ # create! entryable: Comment.new(content: content), creator: creator, account: account
113
113
  # end
114
114
  # end
115
115
  #
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  #
139
139
  # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
140
140
  #
141
- # == Nested Attributes
141
+ # == Nested \Attributes
142
142
  #
143
143
  # Enabling nested attributes on a delegated_type association allows you to
144
144
  # create the entry and message in one go:
@@ -192,6 +192,11 @@ module ActiveRecord
192
192
  # +role+ with an "_id" suffix. So a class that defines a
193
193
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
194
194
  # the default <tt>:foreign_key</tt>.
195
+ # [:foreign_type]
196
+ # Specify the column used to store the associated object's type. By default this is inferred to be the passed
197
+ # +role+ with a "_type" suffix. A class that defines a
198
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
199
+ # the default <tt>:foreign_type</tt>.
195
200
  # [:primary_key]
196
201
  # Specify the method that returns the primary key of associated object used for the convenience methods.
197
202
  # By default this is +id+.
@@ -211,11 +216,11 @@ module ActiveRecord
211
216
  private
212
217
  def define_delegated_type_methods(role, types:, options:)
213
218
  primary_key = options[:primary_key] || "id"
214
- role_type = "#{role}_type"
219
+ role_type = options[:foreign_type] || "#{role}_type"
215
220
  role_id = options[:foreign_key] || "#{role}_id"
216
221
 
217
222
  define_method "#{role}_class" do
218
- public_send("#{role}_type").constantize
223
+ public_send(role_type).constantize
219
224
  end
220
225
 
221
226
  define_method "#{role}_name" do
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  class DestroyAssociationAsyncError < StandardError
5
5
  end
6
6
 
7
+ # = Active Record Destroy Association Async Job
8
+ #
7
9
  # Job to destroy the records associated with a destroyed record in background.
8
10
  class DestroyAssociationAsyncJob < ActiveJob::Base
9
11
  queue_as { ActiveRecord.queues[:destroy] }
@@ -17,7 +19,7 @@ module ActiveRecord
17
19
  )
18
20
  association_model = association_class.constantize
19
21
  owner_class = owner_model_name.constantize
20
- owner = owner_class.find_by(owner_class.primary_key.to_sym => owner_id)
22
+ owner = owner_class.find_by(owner_class.primary_key => [owner_id])
21
23
 
22
24
  if !owner_destroyed?(owner, ensuring_owner_was_method)
23
25
  raise DestroyAssociationAsyncError, "owner record not destroyed"