activerecord 7.1.3.2 → 7.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +570 -2094
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_one_association.rb +2 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/join_dependency.rb +6 -8
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +34 -273
  28. data/lib/active_record/attribute_assignment.rb +1 -11
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  31. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  32. data/lib/active_record/attribute_methods/read.rb +4 -16
  33. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  35. data/lib/active_record/attribute_methods/write.rb +3 -3
  36. data/lib/active_record/attribute_methods.rb +89 -58
  37. data/lib/active_record/attributes.rb +60 -45
  38. data/lib/active_record/autosave_association.rb +17 -31
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +244 -58
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -75
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  47. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  48. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  49. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  50. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  53. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  56. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  57. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  58. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  61. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  64. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  65. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  66. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  67. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  68. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  69. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  74. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  75. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  76. data/lib/active_record/connection_adapters.rb +121 -0
  77. data/lib/active_record/connection_handling.rb +56 -41
  78. data/lib/active_record/core.rb +60 -39
  79. data/lib/active_record/counter_cache.rb +23 -10
  80. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  81. data/lib/active_record/database_configurations/database_config.rb +19 -4
  82. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  83. data/lib/active_record/database_configurations/url_config.rb +20 -1
  84. data/lib/active_record/database_configurations.rb +1 -1
  85. data/lib/active_record/delegated_type.rb +30 -6
  86. data/lib/active_record/destroy_association_async_job.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +2 -2
  88. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  89. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  90. data/lib/active_record/encryption/encryptor.rb +18 -3
  91. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  92. data/lib/active_record/encryption/message_serializer.rb +4 -0
  93. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  95. data/lib/active_record/encryption/scheme.rb +8 -4
  96. data/lib/active_record/enum.rb +26 -6
  97. data/lib/active_record/errors.rb +46 -20
  98. data/lib/active_record/explain.rb +13 -24
  99. data/lib/active_record/fixtures.rb +37 -31
  100. data/lib/active_record/future_result.rb +17 -4
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +4 -2
  103. data/lib/active_record/insert_all.rb +18 -15
  104. data/lib/active_record/integration.rb +4 -1
  105. data/lib/active_record/internal_metadata.rb +48 -34
  106. data/lib/active_record/locking/optimistic.rb +8 -7
  107. data/lib/active_record/log_subscriber.rb +0 -21
  108. data/lib/active_record/marshalling.rb +1 -1
  109. data/lib/active_record/message_pack.rb +2 -2
  110. data/lib/active_record/migration/command_recorder.rb +2 -3
  111. data/lib/active_record/migration/compatibility.rb +11 -3
  112. data/lib/active_record/migration/default_strategy.rb +4 -5
  113. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  114. data/lib/active_record/migration.rb +85 -76
  115. data/lib/active_record/model_schema.rb +39 -70
  116. data/lib/active_record/nested_attributes.rb +11 -3
  117. data/lib/active_record/normalization.rb +3 -7
  118. data/lib/active_record/persistence.rb +32 -354
  119. data/lib/active_record/query_cache.rb +18 -6
  120. data/lib/active_record/query_logs.rb +15 -0
  121. data/lib/active_record/query_logs_formatter.rb +1 -1
  122. data/lib/active_record/querying.rb +21 -9
  123. data/lib/active_record/railtie.rb +54 -67
  124. data/lib/active_record/railties/controller_runtime.rb +13 -4
  125. data/lib/active_record/railties/databases.rake +42 -45
  126. data/lib/active_record/reflection.rb +102 -37
  127. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  128. data/lib/active_record/relation/batches.rb +14 -8
  129. data/lib/active_record/relation/calculations.rb +95 -62
  130. data/lib/active_record/relation/delegation.rb +8 -11
  131. data/lib/active_record/relation/finder_methods.rb +16 -2
  132. data/lib/active_record/relation/merger.rb +4 -6
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  134. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  135. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  136. data/lib/active_record/relation/predicate_builder.rb +3 -3
  137. data/lib/active_record/relation/query_methods.rb +212 -47
  138. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  139. data/lib/active_record/relation/spawn_methods.rb +2 -18
  140. data/lib/active_record/relation/where_clause.rb +7 -19
  141. data/lib/active_record/relation.rb +500 -66
  142. data/lib/active_record/result.rb +32 -45
  143. data/lib/active_record/runtime_registry.rb +39 -0
  144. data/lib/active_record/sanitization.rb +24 -19
  145. data/lib/active_record/schema.rb +8 -6
  146. data/lib/active_record/schema_dumper.rb +19 -9
  147. data/lib/active_record/schema_migration.rb +30 -14
  148. data/lib/active_record/scoping/named.rb +1 -0
  149. data/lib/active_record/signed_id.rb +20 -1
  150. data/lib/active_record/statement_cache.rb +7 -7
  151. data/lib/active_record/table_metadata.rb +1 -10
  152. data/lib/active_record/tasks/database_tasks.rb +87 -48
  153. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  154. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  155. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  156. data/lib/active_record/test_fixtures.rb +87 -89
  157. data/lib/active_record/testing/query_assertions.rb +121 -0
  158. data/lib/active_record/timestamp.rb +5 -3
  159. data/lib/active_record/token_for.rb +22 -12
  160. data/lib/active_record/touch_later.rb +1 -1
  161. data/lib/active_record/transaction.rb +132 -0
  162. data/lib/active_record/transactions.rb +70 -14
  163. data/lib/active_record/translation.rb +0 -2
  164. data/lib/active_record/type/serialized.rb +1 -3
  165. data/lib/active_record/type_caster/connection.rb +4 -4
  166. data/lib/active_record/validations/associated.rb +9 -3
  167. data/lib/active_record/validations/uniqueness.rb +14 -10
  168. data/lib/active_record/validations.rb +4 -1
  169. data/lib/active_record.rb +150 -41
  170. data/lib/arel/alias_predication.rb +1 -1
  171. data/lib/arel/collectors/bind.rb +2 -0
  172. data/lib/arel/collectors/composite.rb +7 -0
  173. data/lib/arel/collectors/sql_string.rb +1 -1
  174. data/lib/arel/collectors/substitute_binds.rb +1 -1
  175. data/lib/arel/nodes/binary.rb +0 -6
  176. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes.rb +2 -2
  181. data/lib/arel/predications.rb +1 -1
  182. data/lib/arel/select_manager.rb +1 -1
  183. data/lib/arel/tree_manager.rb +8 -3
  184. data/lib/arel/update_manager.rb +2 -1
  185. data/lib/arel/visitors/dot.rb +1 -0
  186. data/lib/arel/visitors/mysql.rb +9 -4
  187. data/lib/arel/visitors/postgresql.rb +1 -12
  188. data/lib/arel/visitors/to_sql.rb +31 -17
  189. data/lib/arel.rb +7 -3
  190. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  191. metadata +18 -12
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
9
9
  # on any database connection adapter. For example:
10
10
  #
11
- # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
11
+ # result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
12
12
  # result # => #<ActiveRecord::Result:0xdeadbeef>
13
13
  #
14
14
  # # Get the column names of the result:
@@ -46,11 +46,14 @@ module ActiveRecord
46
46
  end
47
47
  end
48
48
 
49
- def initialize(columns, rows, column_types = {})
50
- @columns = columns
49
+ def initialize(columns, rows, column_types = nil)
50
+ # We freeze the strings to prevent them getting duped when
51
+ # used as keys in ActiveRecord::Base's @attributes hash
52
+ @columns = columns.each(&:-@).freeze
51
53
  @rows = rows
52
54
  @hash_rows = nil
53
- @column_types = column_types
55
+ @column_types = column_types || EMPTY_HASH
56
+ @column_indexes = nil
54
57
  end
55
58
 
56
59
  # Returns true if this result set includes the column named +name+
@@ -131,7 +134,7 @@ module ActiveRecord
131
134
  end
132
135
 
133
136
  def initialize_copy(other)
134
- @columns = columns.dup
137
+ @columns = columns
135
138
  @rows = rows.dup
136
139
  @column_types = column_types.dup
137
140
  @hash_rows = nil
@@ -142,6 +145,19 @@ module ActiveRecord
142
145
  super
143
146
  end
144
147
 
148
+ def column_indexes # :nodoc:
149
+ @column_indexes ||= begin
150
+ index = 0
151
+ hash = {}
152
+ length = columns.length
153
+ while index < length
154
+ hash[columns[index]] = index
155
+ index += 1
156
+ end
157
+ hash
158
+ end
159
+ end
160
+
145
161
  private
146
162
  def column_type(name, index, type_overrides)
147
163
  type_overrides.fetch(name) do
@@ -152,50 +168,21 @@ module ActiveRecord
152
168
  end
153
169
 
154
170
  def hash_rows
155
- @hash_rows ||=
156
- begin
157
- # We freeze the strings to prevent them getting duped when
158
- # used as keys in ActiveRecord::Base's @attributes hash
159
- columns = @columns.map(&:-@)
160
- length = columns.length
161
- template = nil
162
-
163
- @rows.map { |row|
164
- if template
165
- # We use transform_values to build subsequent rows from the
166
- # hash of the first row. This is faster because we avoid any
167
- # reallocs and in Ruby 2.7+ avoid hashing entirely.
168
- index = -1
169
- template.transform_values do
170
- row[index += 1]
171
- end
172
- else
173
- # In the past we used Hash[columns.zip(row)]
174
- # though elegant, the verbose way is much more efficient
175
- # both time and memory wise cause it avoids a big array allocation
176
- # this method is called a lot and needs to be micro optimised
177
- hash = {}
178
-
179
- index = 0
180
- while index < length
181
- hash[columns[index]] = row[index]
182
- index += 1
183
- end
184
-
185
- # It's possible to select the same column twice, in which case
186
- # we can't use a template
187
- template = hash if hash.length == length
188
-
189
- hash
190
- end
191
- }
192
- end
171
+ # We use transform_values to rows.
172
+ # This is faster because we avoid any reallocs and avoid hashing entirely.
173
+ @hash_rows ||= @rows.map do |row|
174
+ column_indexes.transform_values { |index| row[index] }
175
+ end
193
176
  end
194
177
 
195
- EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
178
+ empty_array = [].freeze
179
+ EMPTY_HASH = {}.freeze
180
+ private_constant :EMPTY_HASH
181
+
182
+ EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
196
183
  private_constant :EMPTY
197
184
 
198
- EMPTY_ASYNC = FutureResult::Complete.new(EMPTY).freeze
185
+ EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
199
186
  private_constant :EMPTY_ASYNC
200
187
  end
201
188
  end
@@ -25,15 +25,54 @@ module ActiveRecord
25
25
  ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
26
  end
27
27
 
28
+ def queries_count
29
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
+ end
31
+
32
+ def queries_count=(count)
33
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
+ end
35
+
36
+ def cached_queries_count
37
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
38
+ end
39
+
40
+ def cached_queries_count=(count)
41
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
42
+ end
43
+
28
44
  def reset
45
+ reset_runtimes
46
+ reset_queries_count
47
+ reset_cached_queries_count
48
+ end
49
+
50
+ def reset_runtimes
29
51
  rt, self.sql_runtime = sql_runtime, 0.0
30
52
  self.async_sql_runtime = 0.0
31
53
  rt
32
54
  end
55
+
56
+ def reset_queries_count
57
+ qc = queries_count
58
+ self.queries_count = 0
59
+ qc
60
+ end
61
+
62
+ def reset_cached_queries_count
63
+ qc = cached_queries_count
64
+ self.cached_queries_count = 0
65
+ qc
66
+ end
33
67
  end
34
68
  end
35
69
 
36
70
  ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
+ unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
+ ActiveRecord::RuntimeRegistry.queries_count += 1
73
+ ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
+ end
75
+
37
76
  runtime = (finish - start) * 1_000.0
38
77
 
39
78
  if payload[:async]
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
18
  # # => "name='foo''bar' and group_id='4'"
19
19
  #
20
- # This method will NOT sanitize a SQL string since it won't contain
20
+ # This method will NOT sanitize an SQL string since it won't contain
21
21
  # any conditions in it and will return the string as is.
22
22
  #
23
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
53
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
54
54
  #
55
- # This method will NOT sanitize a SQL string since it won't contain
55
+ # This method will NOT sanitize an SQL string since it won't contain
56
56
  # any conditions in it and will return the string as is.
57
57
  #
58
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
@@ -85,7 +85,7 @@ module ActiveRecord
85
85
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
86
86
  disallow_raw_sql!(
87
87
  [condition.first],
88
- permit: connection.column_name_with_order_matcher
88
+ permit: adapter_class.column_name_with_order_matcher
89
89
  )
90
90
 
91
91
  # Ensure we aren't dealing with a subclass of String that might
@@ -163,17 +163,23 @@ module ActiveRecord
163
163
  def sanitize_sql_array(ary)
164
164
  statement, *values = ary
165
165
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
166
- replace_named_bind_variables(statement, values.first)
166
+ with_connection do |c|
167
+ replace_named_bind_variables(c, statement, values.first)
168
+ end
167
169
  elsif statement.include?("?")
168
- replace_bind_variables(statement, values)
170
+ with_connection do |c|
171
+ replace_bind_variables(c, statement, values)
172
+ end
169
173
  elsif statement.blank?
170
174
  statement
171
175
  else
172
- statement % values.collect { |value| connection.quote_string(value.to_s) }
176
+ with_connection do |c|
177
+ statement % values.collect { |value| c.quote_string(value.to_s) }
178
+ end
173
179
  end
174
180
  end
175
181
 
176
- def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
182
+ def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
177
183
  unexpected = nil
178
184
  args.each do |arg|
179
185
  next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
@@ -193,48 +199,47 @@ module ActiveRecord
193
199
  end
194
200
 
195
201
  private
196
- def replace_bind_variables(statement, values)
202
+ def replace_bind_variables(connection, statement, values)
197
203
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
198
204
  bound = values.dup
199
- c = connection
200
205
  statement.gsub(/\?/) do
201
- replace_bind_variable(bound.shift, c)
206
+ replace_bind_variable(connection, bound.shift)
202
207
  end
203
208
  end
204
209
 
205
- def replace_bind_variable(value, c = connection)
210
+ def replace_bind_variable(connection, value)
206
211
  if ActiveRecord::Relation === value
207
212
  value.to_sql
208
213
  else
209
- quote_bound_value(value, c)
214
+ quote_bound_value(connection, value)
210
215
  end
211
216
  end
212
217
 
213
- def replace_named_bind_variables(statement, bind_vars)
218
+ def replace_named_bind_variables(connection, statement, bind_vars)
214
219
  statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
215
- if $1 == ":" # skip postgresql casts
220
+ if $1 == ":" # skip PostgreSQL casts
216
221
  match # return the whole match
217
222
  elsif $1 == "\\" # escaped literal colon
218
223
  match[1..-1] # return match with escaping backlash char removed
219
224
  elsif bind_vars.include?(match = $2.to_sym)
220
- replace_bind_variable(bind_vars[match])
225
+ replace_bind_variable(connection, bind_vars[match])
221
226
  else
222
227
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
223
228
  end
224
229
  end
225
230
  end
226
231
 
227
- def quote_bound_value(value, c = connection)
232
+ def quote_bound_value(connection, value)
228
233
  if value.respond_to?(:map) && !value.acts_like?(:string)
229
234
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
230
235
  if values.empty?
231
- c.quote(c.cast_bound_value(nil))
236
+ connection.quote(connection.cast_bound_value(nil))
232
237
  else
233
- values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
238
+ values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
234
239
  end
235
240
  else
236
241
  value = value.id_for_database if value.respond_to?(:id_for_database)
237
- c.quote(c.cast_bound_value(value))
242
+ connection.quote(connection.cast_bound_value(value))
238
243
  end
239
244
  end
240
245
 
@@ -52,14 +52,16 @@ module ActiveRecord
52
52
  end
53
53
 
54
54
  def define(info, &block) # :nodoc:
55
- instance_eval(&block)
55
+ connection_pool.with_connection do |connection|
56
+ instance_eval(&block)
56
57
 
57
- connection.schema_migration.create_table
58
- if info[:version].present?
59
- connection.assume_migrated_upto_version(info[:version])
60
- end
58
+ connection_pool.schema_migration.create_table
59
+ if info[:version].present?
60
+ connection.assume_migrated_upto_version(info[:version])
61
+ end
61
62
 
62
- connection.internal_metadata.create_table_and_set_flags(connection.migration_context.current_environment)
63
+ connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
64
+ end
63
65
  end
64
66
  end
65
67
 
@@ -41,8 +41,10 @@ module ActiveRecord
41
41
  cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
42
42
 
43
43
  class << self
44
- def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
45
- connection.create_schema_dumper(generate_options(config)).dump(stream)
44
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
45
+ pool.with_connection do |connection|
46
+ connection.create_schema_dumper(generate_options(config)).dump(stream)
47
+ end
46
48
  stream
47
49
  end
48
50
 
@@ -70,7 +72,7 @@ module ActiveRecord
70
72
 
71
73
  def initialize(connection, options = {})
72
74
  @connection = connection
73
- @version = connection.migration_context.current_version rescue nil
75
+ @version = connection.pool.migration_context.current_version rescue nil
74
76
  @options = options
75
77
  @ignore_tables = [
76
78
  ActiveRecord::Base.schema_migrations_table_name,
@@ -127,15 +129,24 @@ module ActiveRecord
127
129
  def tables(stream)
128
130
  sorted_tables = @connection.tables.sort
129
131
 
130
- sorted_tables.each do |table_name|
131
- table(table_name, stream) unless ignored?(table_name)
132
+ not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
133
+
134
+ not_ignored_tables.each_with_index do |table_name, index|
135
+ table(table_name, stream)
136
+ stream.puts if index < not_ignored_tables.count - 1
132
137
  end
133
138
 
134
139
  # dump foreign keys at the end to make sure all dependent tables exist.
135
- if @connection.use_foreign_keys?
136
- sorted_tables.each do |tbl|
137
- foreign_keys(tbl, stream) unless ignored?(tbl)
140
+ if @connection.supports_foreign_keys?
141
+ foreign_keys_stream = StringIO.new
142
+ not_ignored_tables.each do |tbl|
143
+ foreign_keys(tbl, foreign_keys_stream)
138
144
  end
145
+
146
+ foreign_keys_string = foreign_keys_stream.string
147
+ stream.puts if foreign_keys_string.length > 0
148
+
149
+ stream.print foreign_keys_string
139
150
  end
140
151
  end
141
152
 
@@ -196,7 +207,6 @@ module ActiveRecord
196
207
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
197
208
 
198
209
  tbl.puts " end"
199
- tbl.puts
200
210
 
201
211
  stream.print tbl.string
202
212
  rescue => e
@@ -9,29 +9,35 @@ module ActiveRecord
9
9
  class NullSchemaMigration # :nodoc:
10
10
  end
11
11
 
12
- attr_reader :connection, :arel_table
12
+ attr_reader :arel_table
13
13
 
14
- def initialize(connection)
15
- @connection = connection
14
+ def initialize(pool)
15
+ @pool = pool
16
16
  @arel_table = Arel::Table.new(table_name)
17
17
  end
18
18
 
19
19
  def create_version(version)
20
20
  im = Arel::InsertManager.new(arel_table)
21
21
  im.insert(arel_table[primary_key] => version)
22
- connection.insert(im, "#{self.class} Create", primary_key, version)
22
+ @pool.with_connection do |connection|
23
+ connection.insert(im, "#{self.class} Create", primary_key, version)
24
+ end
23
25
  end
24
26
 
25
27
  def delete_version(version)
26
28
  dm = Arel::DeleteManager.new(arel_table)
27
29
  dm.wheres = [arel_table[primary_key].eq(version)]
28
30
 
29
- connection.delete(dm, "#{self.class} Destroy")
31
+ @pool.with_connection do |connection|
32
+ connection.delete(dm, "#{self.class} Destroy")
33
+ end
30
34
  end
31
35
 
32
36
  def delete_all_versions
33
- versions.each do |version|
34
- delete_version(version)
37
+ @pool.with_connection do |connection|
38
+ versions.each do |version|
39
+ delete_version(version)
40
+ end
35
41
  end
36
42
  end
37
43
 
@@ -44,15 +50,19 @@ module ActiveRecord
44
50
  end
45
51
 
46
52
  def create_table
47
- unless connection.table_exists?(table_name)
48
- connection.create_table(table_name, id: false) do |t|
49
- t.string :version, **connection.internal_string_options_for_primary_key
53
+ @pool.with_connection do |connection|
54
+ unless connection.table_exists?(table_name)
55
+ connection.create_table(table_name, id: false) do |t|
56
+ t.string :version, **connection.internal_string_options_for_primary_key
57
+ end
50
58
  end
51
59
  end
52
60
  end
53
61
 
54
62
  def drop_table
55
- connection.drop_table table_name, if_exists: true
63
+ @pool.with_connection do |connection|
64
+ connection.drop_table table_name, if_exists: true
65
+ end
56
66
  end
57
67
 
58
68
  def normalize_migration_number(number)
@@ -68,7 +78,9 @@ module ActiveRecord
68
78
  sm.project(arel_table[primary_key])
69
79
  sm.order(arel_table[primary_key].asc)
70
80
 
71
- connection.select_values(sm, "#{self.class} Load")
81
+ @pool.with_connection do |connection|
82
+ connection.select_values(sm, "#{self.class} Load")
83
+ end
72
84
  end
73
85
 
74
86
  def integer_versions
@@ -79,11 +91,15 @@ module ActiveRecord
79
91
  sm = Arel::SelectManager.new(arel_table)
80
92
  sm.project(*Arel::Nodes::Count.new([Arel.star]))
81
93
 
82
- connection.select_values(sm, "#{self.class} Count").first
94
+ @pool.with_connection do |connection|
95
+ connection.select_values(sm, "#{self.class} Count").first
96
+ end
83
97
  end
84
98
 
85
99
  def table_exists?
86
- connection.data_source_exists?(table_name)
100
+ @pool.with_connection do |connection|
101
+ connection.data_source_exists?(table_name)
102
+ end
87
103
  end
88
104
  end
89
105
  end
@@ -190,6 +190,7 @@ module ActiveRecord
190
190
 
191
191
  private
192
192
  def singleton_method_added(name)
193
+ super
193
194
  generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
194
195
  end
195
196
  end
@@ -13,6 +13,16 @@ module ActiveRecord
13
13
  class_attribute :signed_id_verifier_secret, instance_writer: false
14
14
  end
15
15
 
16
+ module RelationMethods # :nodoc:
17
+ def find_signed(...)
18
+ scoping { model.find_signed(...) }
19
+ end
20
+
21
+ def find_signed!(...)
22
+ scoping { model.find_signed!(...) }
23
+ end
24
+ end
25
+
16
26
  module ClassMethods
17
27
  # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
18
28
  # This is particularly useful for things like password reset or email verification, where you want
@@ -76,7 +86,7 @@ module ActiveRecord
76
86
  if secret.nil?
77
87
  raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
78
88
  else
79
- ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
89
+ ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
80
90
  end
81
91
  end
82
92
  end
@@ -96,7 +106,16 @@ module ActiveRecord
96
106
 
97
107
 
98
108
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
109
+ #
99
110
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
111
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
112
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
113
+ # It's just encoded and protected against tampering.
114
+ #
115
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
116
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
117
+ # when passed to +find_signed+ (or raise with +find_signed!+).
118
+ #
100
119
  # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
120
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
121
  # record. If a purpose is set, this too must match.
@@ -4,14 +4,14 @@ module ActiveRecord
4
4
  # Statement cache is used to cache a single statement in order to avoid creating the AST again.
5
5
  # Initializing the cache is done by passing the statement in the create block:
6
6
  #
7
- # cache = StatementCache.create(Book.connection) do |params|
7
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
8
8
  # Book.where(name: "my book").where("author_id > 3")
9
9
  # end
10
10
  #
11
11
  # The cached statement is executed by using the
12
12
  # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
13
13
  #
14
- # cache.execute([], Book.connection)
14
+ # cache.execute([], ClothingItem.lease_connection)
15
15
  #
16
16
  # The relation returned by the block is cached, and for each
17
17
  # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
@@ -20,13 +20,13 @@ module ActiveRecord
20
20
  # If you want to cache the statement without the values you can use the +bind+ method of the
21
21
  # block parameter.
22
22
  #
23
- # cache = StatementCache.create(Book.connection) do |params|
23
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
24
24
  # Book.where(name: params.bind)
25
25
  # end
26
26
  #
27
27
  # And pass the bind values as the first argument of +execute+ call.
28
28
  #
29
- # cache.execute(["my book"], Book.connection)
29
+ # cache.execute(["my book"], ClothingItem.lease_connection)
30
30
  class StatementCache # :nodoc:
31
31
  class Substitute; end # :nodoc:
32
32
 
@@ -62,7 +62,7 @@ module ActiveRecord
62
62
  end
63
63
 
64
64
  class PartialQueryCollector
65
- attr_accessor :preparable
65
+ attr_accessor :preparable, :retryable
66
66
 
67
67
  def initialize
68
68
  @parts = []
@@ -142,12 +142,12 @@ module ActiveRecord
142
142
  @klass = klass
143
143
  end
144
144
 
145
- def execute(params, connection, &block)
145
+ def execute(params, connection, allow_retry: false, &block)
146
146
  bind_values = bind_map.bind params
147
147
 
148
148
  sql = query_builder.sql_for bind_values, connection
149
149
 
150
- klass.find_by_sql(sql, bind_values, preparable: true, &block)
150
+ klass.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
151
151
  rescue ::RangeError
152
152
  []
153
153
  end
@@ -23,16 +23,7 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def associated_with?(table_name)
26
- if reflection = klass&._reflect_on_association(table_name)
27
- reflection
28
- elsif ActiveRecord.allow_deprecated_singular_associations_name && reflection = klass&._reflect_on_association(table_name.singularize)
29
- ActiveRecord.deprecator.warn(<<~MSG)
30
- Referring to a singular association (e.g. `#{reflection.name}`) by its plural name (e.g. `#{reflection.plural_name}`) is deprecated.
31
-
32
- To convert this deprecation warning to an error and enable more performant behavior, set config.active_record.allow_deprecated_singular_associations_name = false.
33
- MSG
34
- reflection
35
- end
26
+ klass&._reflect_on_association(table_name)
36
27
  end
37
28
 
38
29
  def associated_table(table_name)