activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  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 +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  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 +16 -8
  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 +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  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/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -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,47 +168,18 @@ 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
185
  EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
@@ -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
 
@@ -191,12 +202,16 @@ module ActiveRecord
191
202
  end
192
203
 
193
204
  indexes_in_create(table, tbl)
194
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
205
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
195
206
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
196
207
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
197
208
 
198
209
  tbl.puts " end"
199
- tbl.puts
210
+
211
+ if remaining
212
+ tbl.puts
213
+ tbl.print remaining.string
214
+ end
200
215
 
201
216
  stream.print tbl.string
202
217
  rescue => e
@@ -262,24 +277,37 @@ module ActiveRecord
262
277
 
263
278
  def check_constraints_in_create(table, stream)
264
279
  if (check_constraints = @connection.check_constraints(table)).any?
265
- add_check_constraint_statements = check_constraints.map do |check_constraint|
266
- parts = [
267
- "t.check_constraint #{check_constraint.expression.inspect}"
268
- ]
280
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
269
281
 
270
- if check_constraint.export_name_on_schema_dump?
271
- parts << "name: #{check_constraint.name.inspect}"
282
+ unless check_valid.empty?
283
+ check_constraint_statements = check_valid.map do |check|
284
+ " t.check_constraint #{check_parts(check).join(', ')}"
272
285
  end
273
286
 
274
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
275
-
276
- " #{parts.join(', ')}"
287
+ stream.puts check_constraint_statements.sort.join("\n")
277
288
  end
278
289
 
279
- stream.puts add_check_constraint_statements.sort.join("\n")
290
+ unless check_invalid.empty?
291
+ remaining = StringIO.new
292
+ table_name = remove_prefix_and_suffix(table).inspect
293
+
294
+ add_check_constraint_statements = check_invalid.map do |check|
295
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
296
+ end
297
+
298
+ remaining.puts add_check_constraint_statements.sort.join("\n")
299
+ remaining
300
+ end
280
301
  end
281
302
  end
282
303
 
304
+ def check_parts(check)
305
+ check_parts = [ check.expression.inspect ]
306
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
307
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
308
+ check_parts
309
+ end
310
+
283
311
  def foreign_keys(table, stream)
284
312
  if (foreign_keys = @connection.foreign_keys(table)).any?
285
313
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
@@ -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
@@ -30,13 +30,13 @@ module ActiveRecord
30
30
  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
31
  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
32
  #
33
- # === Options
33
+ # ==== Options
34
34
  #
35
- # [:length]
35
+ # [+:length+]
36
36
  # Length of the Secure Random, with a minimum of 24 characters. It will
37
37
  # default to 24.
38
38
  #
39
- # [:on]
39
+ # [+:on+]
40
40
  # The callback when the value is generated. When called with <tt>on:
41
41
  # :initialize</tt>, the value is generated in an
42
42
  # <tt>after_initialize</tt> callback, otherwise the value will be used
@@ -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
@@ -47,12 +57,12 @@ module ActiveRecord
47
57
  end
48
58
  end
49
59
 
50
- # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
60
+ # Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
51
61
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
52
- # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
62
+ # or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
53
63
  # the valid signed id can't find a record.
54
64
  #
55
- # === Examples
65
+ # ==== Examples
56
66
  #
57
67
  # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
58
68
  #
@@ -66,8 +76,9 @@ module ActiveRecord
66
76
  end
67
77
 
68
78
  # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
69
- # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
70
- # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
79
+ # with the class-level +signed_id_verifier_secret+, which within Rails comes from
80
+ # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
81
+ # By default, it's SHA256 for the digest and JSON for the serialization.
71
82
  def signed_id_verifier
72
83
  @signed_id_verifier ||= begin
73
84
  secret = signed_id_verifier_secret
@@ -76,14 +87,14 @@ module ActiveRecord
76
87
  if secret.nil?
77
88
  raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
78
89
  else
79
- ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
90
+ ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
80
91
  end
81
92
  end
82
93
  end
83
94
 
84
95
  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
85
96
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
86
- # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
97
+ # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
87
98
  def signed_id_verifier=(verifier)
88
99
  @signed_id_verifier = verifier
89
100
  end
@@ -96,7 +107,16 @@ module ActiveRecord
96
107
 
97
108
 
98
109
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
110
+ #
99
111
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
112
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
113
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
114
+ # It's just encoded and protected against tampering.
115
+ #
116
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
117
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
118
+ # when passed to +find_signed+ (or raise with +find_signed!+).
119
+ #
100
120
  # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
121
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
122
  # record. If a purpose is set, this too must match.