activerecord 7.2.2.2 → 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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +316 -7
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/alias_tracker.rb +6 -4
  5. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  6. data/lib/active_record/associations/collection_association.rb +9 -7
  7. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  8. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  9. data/lib/active_record/attribute_methods.rb +24 -19
  10. data/lib/active_record/attributes.rb +37 -26
  11. data/lib/active_record/autosave_association.rb +22 -12
  12. data/lib/active_record/base.rb +2 -2
  13. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
  14. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
  15. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
  16. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
  18. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
  19. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  20. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
  21. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
  22. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
  23. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  24. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
  26. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  27. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
  29. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  30. data/lib/active_record/connection_handling.rb +12 -8
  31. data/lib/active_record/core.rb +27 -7
  32. data/lib/active_record/counter_cache.rb +1 -1
  33. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  34. data/lib/active_record/delegated_type.rb +18 -18
  35. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  36. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  37. data/lib/active_record/encryption/encryptor.rb +21 -20
  38. data/lib/active_record/enum.rb +13 -12
  39. data/lib/active_record/errors.rb +3 -3
  40. data/lib/active_record/fixture_set/table_row.rb +19 -2
  41. data/lib/active_record/gem_version.rb +2 -2
  42. data/lib/active_record/migration.rb +2 -1
  43. data/lib/active_record/query_logs.rb +4 -0
  44. data/lib/active_record/querying.rb +4 -4
  45. data/lib/active_record/railtie.rb +2 -2
  46. data/lib/active_record/railties/databases.rake +2 -1
  47. data/lib/active_record/relation/calculations.rb +35 -30
  48. data/lib/active_record/relation/finder_methods.rb +10 -10
  49. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  50. data/lib/active_record/relation/query_attribute.rb +1 -1
  51. data/lib/active_record/relation/query_methods.rb +16 -9
  52. data/lib/active_record/relation/where_clause.rb +8 -2
  53. data/lib/active_record/relation.rb +15 -5
  54. data/lib/active_record/schema_dumper.rb +29 -11
  55. data/lib/active_record/secure_token.rb +3 -3
  56. data/lib/active_record/signed_id.rb +7 -6
  57. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
  58. data/lib/active_record/transactions.rb +3 -1
  59. data/lib/active_record.rb +1 -1
  60. data/lib/arel/collectors/bind.rb +1 -1
  61. data/lib/arel/crud.rb +2 -0
  62. data/lib/arel/delete_manager.rb +5 -0
  63. data/lib/arel/nodes/delete_statement.rb +4 -2
  64. data/lib/arel/nodes/update_statement.rb +4 -2
  65. data/lib/arel/select_manager.rb +6 -2
  66. data/lib/arel/update_manager.rb +5 -0
  67. data/lib/arel/visitors/dot.rb +2 -0
  68. data/lib/arel/visitors/to_sql.rb +3 -1
  69. metadata +8 -8
@@ -46,8 +46,8 @@ module ActiveRecord
46
46
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
47
47
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
48
48
  #
49
- # Note that building your own SQL query string from user input may expose your application to
50
- # injection attacks (https://guides.rubyonrails.org/security.html#sql-injection).
49
+ # Note that building your own SQL query string from user input {may expose your application to
50
+ # injection attacks}[https://guides.rubyonrails.org/security.html#sql-injection].
51
51
  def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
52
52
  result = with_connection do |c|
53
53
  _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
@@ -55,7 +55,7 @@ module ActiveRecord
55
55
  _load_from_sql(result, &block)
56
56
  end
57
57
 
58
- # Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
58
+ # Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
59
59
  def async_find_by_sql(sql, binds = [], preparable: nil, &block)
60
60
  result = with_connection do |c|
61
61
  _query_by_sql(c, sql, binds, preparable: preparable, async: true)
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
  end
113
113
  end
114
114
 
115
- # Same as <tt>#count_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
115
+ # Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
116
116
  def async_count_by_sql(sql)
117
117
  with_connection do |c|
118
118
  c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
@@ -23,8 +23,8 @@ module ActiveRecord
23
23
  config.action_dispatch.rescue_responses.merge!(
24
24
  "ActiveRecord::RecordNotFound" => :not_found,
25
25
  "ActiveRecord::StaleObjectError" => :conflict,
26
- "ActiveRecord::RecordInvalid" => :unprocessable_entity,
27
- "ActiveRecord::RecordNotSaved" => :unprocessable_entity
26
+ "ActiveRecord::RecordInvalid" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
27
+ "ActiveRecord::RecordNotSaved" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT
28
28
  )
29
29
 
30
30
  config.active_record.use_schema_cache_dump = true
@@ -473,7 +473,8 @@ db_namespace = namespace :db do
473
473
 
474
474
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
475
475
  task load: [:load_config, :check_protected_environments] do
476
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord.schema_format, ENV["SCHEMA"])
476
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
477
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(schema_format, ENV["SCHEMA"])
477
478
  end
478
479
 
479
480
  namespace :dump do
@@ -60,37 +60,37 @@ module ActiveRecord
60
60
  # Person.distinct.count(:age)
61
61
  # # => counts the number of different age values
62
62
  #
63
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
63
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
64
64
  # it returns a Hash whose keys represent the aggregated column,
65
65
  # and the values are the respective amounts:
66
66
  #
67
67
  # Person.group(:city).count
68
68
  # # => { 'Rome' => 5, 'Paris' => 3 }
69
69
  #
70
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
70
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
71
71
  # keys are an array containing the individual values of each column and the value
72
- # of each key would be the #count.
72
+ # of each key would be the count.
73
73
  #
74
74
  # Article.group(:status, :category).count
75
75
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
- # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
77
+ # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
79
79
  # Person.select(:age).count
80
80
  # # => counts the number of different age values
81
81
  #
82
- # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
82
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
83
83
  # between databases. In invalid cases, an error from the database is thrown.
84
84
  #
85
- # When given a block, loads all records in the relation, if the relation
86
- # hasn't been loaded yet. Calls the block with each record in the relation.
87
- # Returns the number of records for which the block returns a truthy value.
85
+ # When given a block, calls the block with each record in the relation and
86
+ # returns the number of records for which the block returns a truthy value.
88
87
  #
89
88
  # Person.count { |person| person.age > 21 }
90
89
  # # => counts the number of people older that 21
91
90
  #
92
- # Note: If there are a lot of records in the relation, loading all records
93
- # could result in performance issues.
91
+ # If the relation hasn't been loaded yet, calling +count+ with a block will
92
+ # load all records in the relation. If there are a lot of records in the
93
+ # relation, loading all records could result in performance issues.
94
94
  def count(column_name = nil)
95
95
  if block_given?
96
96
  unless column_name.nil?
@@ -159,16 +159,15 @@ module ActiveRecord
159
159
  #
160
160
  # Person.sum(:age) # => 4562
161
161
  #
162
- # When given a block, loads all records in the relation, if the relation
163
- # hasn't been loaded yet. Calls the block with each record in the relation.
164
- # Returns the sum of +initial_value_or_column+ and the block return
165
- # values:
162
+ # When given a block, calls the block with each record in the relation and
163
+ # returns the sum of +initial_value_or_column+ plus the block return values:
166
164
  #
167
165
  # Person.sum { |person| person.age } # => 4562
168
166
  # Person.sum(1000) { |person| person.age } # => 5562
169
167
  #
170
- # Note: If there are a lot of records in the relation, loading all records
171
- # could result in performance issues.
168
+ # If the relation hasn't been loaded yet, calling +sum+ with a block will
169
+ # load all records in the relation. If there are a lot of records in the
170
+ # relation, loading all records could result in performance issues.
172
171
  def sum(initial_value_or_column = 0, &block)
173
172
  if block_given?
174
173
  map(&block).sum(initial_value_or_column)
@@ -406,6 +405,15 @@ module ActiveRecord
406
405
  async.ids
407
406
  end
408
407
 
408
+ protected
409
+ def aggregate_column(column_name)
410
+ return column_name if Arel::Expressions === column_name
411
+
412
+ arel_column(column_name.to_s) do |name|
413
+ column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
414
+ end
415
+ end
416
+
409
417
  private
410
418
  def all_attributes?(column_names)
411
419
  (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
@@ -446,14 +454,6 @@ module ActiveRecord
446
454
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
447
455
  end
448
456
 
449
- def aggregate_column(column_name)
450
- return column_name if Arel::Expressions === column_name
451
-
452
- arel_column(column_name.to_s) do |name|
453
- column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
454
- end
455
- end
456
-
457
457
  def operation_over_aggregate_column(column, operation, distinct)
458
458
  operation == "count" ? column.count(distinct) : column.public_send(operation)
459
459
  end
@@ -469,7 +469,7 @@ module ActiveRecord
469
469
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
470
470
  relation = unscope(:order).distinct!(false)
471
471
 
472
- column = aggregate_column(column_name)
472
+ column = relation.aggregate_column(column_name)
473
473
  select_value = operation_over_aggregate_column(column, operation, distinct)
474
474
  select_value.distinct = true if operation == "sum" && distinct
475
475
 
@@ -479,7 +479,11 @@ module ActiveRecord
479
479
  end
480
480
 
481
481
  query_result = if relation.where_clause.contradiction?
482
- ActiveRecord::Result.empty
482
+ if @async
483
+ FutureResult.wrap(ActiveRecord::Result.empty)
484
+ else
485
+ ActiveRecord::Result.empty
486
+ end
483
487
  else
484
488
  skip_query_cache_if_necessary do
485
489
  @klass.with_connection do |c|
@@ -508,7 +512,9 @@ module ActiveRecord
508
512
  associated = association && association.belongs_to? # only count belongs_to associations
509
513
  group_fields = Array(association.foreign_key) if associated
510
514
  end
511
- group_fields = arel_columns(group_fields)
515
+
516
+ relation = except(:group).distinct!(false)
517
+ group_fields = relation.arel_columns(group_fields)
512
518
 
513
519
  @klass.with_connection do |connection|
514
520
  column_alias_tracker = ColumnAliasTracker.new(connection)
@@ -519,7 +525,7 @@ module ActiveRecord
519
525
  }
520
526
  group_columns = group_aliases.zip(group_fields)
521
527
 
522
- column = aggregate_column(column_name)
528
+ column = relation.aggregate_column(column_name)
523
529
  column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
524
530
  select_value = operation_over_aggregate_column(column, operation, distinct)
525
531
  select_value.as(adapter_class.quote_column_name(column_alias))
@@ -536,7 +542,6 @@ module ActiveRecord
536
542
  end
537
543
  }
538
544
 
539
- relation = except(:group).distinct!(false)
540
545
  relation.group_values = group_fields
541
546
  relation.select_values = select_values
542
547
 
@@ -662,7 +667,7 @@ module ActiveRecord
662
667
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
663
668
  else
664
669
  column_alias = Arel.sql("count_column")
665
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
670
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
666
671
  end
667
672
 
668
673
  subquery_alias = Arel.sql("subquery_for_count", retryable: true)
@@ -24,22 +24,22 @@ module ActiveRecord
24
24
  # TravelRoute.primary_key = [:origin, :destination]
25
25
  #
26
26
  # TravelRoute.find(["Ottawa", "London"])
27
- # => #<TravelRoute origin: "Ottawa", destination: "London">
27
+ # # => #<TravelRoute origin: "Ottawa", destination: "London">
28
28
  #
29
29
  # TravelRoute.find([["Paris", "Montreal"]])
30
- # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
30
+ # # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
31
  #
32
32
  # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
- # => [
34
- # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
- # #<TravelRoute origin: "New York", destination: "Portland">
36
- # ]
33
+ # # => [
34
+ # # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # # ]
37
37
  #
38
38
  # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
- # => [
40
- # #<TravelRoute origin: "Berlin", destination: "London">,
41
- # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
- # ]
39
+ # # => [
40
+ # # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # # ]
43
43
  #
44
44
  # NOTE: The returned records are in the same order as the ids you provide.
45
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
@@ -59,6 +59,8 @@ module ActiveRecord
59
59
  def convert_to_id(value)
60
60
  if primary_key.is_a?(Array)
61
61
  primary_key.map do |attribute|
62
+ next nil if value.nil?
63
+
62
64
  if attribute == "id"
63
65
  value.id_value
64
66
  else
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  def nil?
36
36
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
37
37
  value_before_type_cast.nil? ||
38
- type.respond_to?(:subtype) && serializable? && value_for_database.nil?
38
+ (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
39
39
  end
40
40
  end
41
41
 
@@ -491,7 +491,8 @@ module ActiveRecord
491
491
 
492
492
  # Like #with, but modifies relation in place.
493
493
  def with!(*args) # :nodoc:
494
- self.with_values += args
494
+ args = process_with_args(args)
495
+ self.with_values |= args
495
496
  self
496
497
  end
497
498
 
@@ -502,7 +503,7 @@ module ActiveRecord
502
503
  # # WITH post_and_replies AS (
503
504
  # # (SELECT * FROM posts WHERE id = 42)
504
505
  # # UNION ALL
505
- # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
506
+ # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
506
507
  # # )
507
508
  # # SELECT * FROM posts
508
509
  #
@@ -514,7 +515,8 @@ module ActiveRecord
514
515
 
515
516
  # Like #with_recursive but modifies the relation in place.
516
517
  def with_recursive!(*args) # :nodoc:
517
- self.with_values += args
518
+ args = process_with_args(args)
519
+ self.with_values |= args
518
520
  @with_is_recursive = true
519
521
  self
520
522
  end
@@ -1277,13 +1279,13 @@ module ActiveRecord
1277
1279
  #
1278
1280
  # users = User.readonly
1279
1281
  # users.first.save
1280
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1282
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1281
1283
  #
1282
1284
  # To make a readonly relation writable, pass +false+.
1283
1285
  #
1284
1286
  # users.readonly(false)
1285
1287
  # users.first.save
1286
- # => true
1288
+ # # => true
1287
1289
  def readonly(value = true)
1288
1290
  spawn.readonly!(value)
1289
1291
  end
@@ -1298,7 +1300,7 @@ module ActiveRecord
1298
1300
  #
1299
1301
  # user = User.strict_loading.first
1300
1302
  # user.comments.to_a
1301
- # => ActiveRecord::StrictLoadingViolationError
1303
+ # # => ActiveRecord::StrictLoadingViolationError
1302
1304
  def strict_loading(value = true)
1303
1305
  spawn.strict_loading!(value)
1304
1306
  end
@@ -1896,8 +1898,6 @@ module ActiveRecord
1896
1898
  return if with_values.empty?
1897
1899
 
1898
1900
  with_statements = with_values.map do |with_value|
1899
- raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
-
1901
1901
  build_with_value_from_hash(with_value)
1902
1902
  end
1903
1903
 
@@ -2096,7 +2096,7 @@ module ActiveRecord
2096
2096
  arg.expr.relation.name
2097
2097
  end
2098
2098
  end
2099
- end.compact
2099
+ end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
2100
2100
  end
2101
2101
 
2102
2102
  def extract_table_name_from(string)
@@ -2208,6 +2208,13 @@ module ActiveRecord
2208
2208
  end
2209
2209
  end
2210
2210
 
2211
+ def process_with_args(args)
2212
+ args.flat_map do |arg|
2213
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2214
+ arg.map { |k, v| { k => v } }
2215
+ end
2216
+ end
2217
+
2211
2218
  STRUCTURAL_VALUE_METHODS = (
2212
2219
  Relation::VALUE_METHODS -
2213
2220
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -135,10 +135,14 @@ module ActiveRecord
135
135
 
136
136
  def extract_attribute(node)
137
137
  attr_node = nil
138
- Arel.fetch_attribute(node) do |attr|
139
- return if attr_node&.!= attr # all attr nodes should be the same
138
+
139
+ valid_attrs = Arel.fetch_attribute(node) do |attr|
140
+ !attr_node || attr_node == attr # all attr nodes should be the same
141
+ ensure
140
142
  attr_node = attr
141
143
  end
144
+ return unless valid_attrs # all nested nodes should yield an attribute
145
+
142
146
  attr_node
143
147
  end
144
148
 
@@ -172,6 +176,8 @@ module ActiveRecord
172
176
  end
173
177
 
174
178
  def except_predicates(columns)
179
+ return predicates if columns.empty?
180
+
175
181
  attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
176
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
177
183
 
@@ -265,7 +265,12 @@ module ActiveRecord
265
265
  # such situation.
266
266
  def create_or_find_by(attributes, &block)
267
267
  with_connection do |connection|
268
- transaction(requires_new: true) { create(attributes, &block) }
268
+ record = nil
269
+ transaction(requires_new: true) do
270
+ record = create(attributes, &block)
271
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
272
+ end
273
+ record
269
274
  rescue ActiveRecord::RecordNotUnique
270
275
  if connection.transaction_open?
271
276
  where(attributes).lock.find_by!(attributes)
@@ -280,7 +285,12 @@ module ActiveRecord
280
285
  # is raised if the created record is invalid.
281
286
  def create_or_find_by!(attributes, &block)
282
287
  with_connection do |connection|
283
- transaction(requires_new: true) { create!(attributes, &block) }
288
+ record = nil
289
+ transaction(requires_new: true) do
290
+ record = create!(attributes, &block)
291
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
292
+ end
293
+ record
284
294
  rescue ActiveRecord::RecordNotUnique
285
295
  if connection.transaction_open?
286
296
  where(attributes).lock.find_by!(attributes)
@@ -290,7 +300,7 @@ module ActiveRecord
290
300
  end
291
301
  end
292
302
 
293
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
303
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
294
304
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
295
305
  def find_or_initialize_by(attributes, &block)
296
306
  find_by(attributes) || new(attributes, &block)
@@ -813,7 +823,7 @@ module ActiveRecord
813
823
  #
814
824
  # [:returning]
815
825
  # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
816
- # inserted records, which by default is the primary key.
826
+ # upserted records, which by default is the primary key.
817
827
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
818
828
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
819
829
  # clause entirely.
@@ -942,7 +952,7 @@ module ActiveRecord
942
952
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
943
953
  # If no time argument is passed, the current time is used as default.
944
954
  #
945
- # === Examples
955
+ # ==== Examples
946
956
  #
947
957
  # # Touch all records
948
958
  # Person.all.touch_all
@@ -202,12 +202,17 @@ module ActiveRecord
202
202
  end
203
203
 
204
204
  indexes_in_create(table, tbl)
205
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
205
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
206
206
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
207
207
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
208
208
 
209
209
  tbl.puts " end"
210
210
 
211
+ if remaining
212
+ tbl.puts
213
+ tbl.print remaining.string
214
+ end
215
+
211
216
  stream.print tbl.string
212
217
  rescue => e
213
218
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -272,24 +277,37 @@ module ActiveRecord
272
277
 
273
278
  def check_constraints_in_create(table, stream)
274
279
  if (check_constraints = @connection.check_constraints(table)).any?
275
- add_check_constraint_statements = check_constraints.map do |check_constraint|
276
- parts = [
277
- "t.check_constraint #{check_constraint.expression.inspect}"
278
- ]
280
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
279
281
 
280
- if check_constraint.export_name_on_schema_dump?
281
- 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(', ')}"
282
285
  end
283
286
 
284
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
285
-
286
- " #{parts.join(', ')}"
287
+ stream.puts check_constraint_statements.sort.join("\n")
287
288
  end
288
289
 
289
- 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
290
301
  end
291
302
  end
292
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
+
293
311
  def foreign_keys(table, stream)
294
312
  if (foreign_keys = @connection.foreign_keys(table)).any?
295
313
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
@@ -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
@@ -57,12 +57,12 @@ module ActiveRecord
57
57
  end
58
58
  end
59
59
 
60
- # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
60
+ # Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
61
61
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
62
- # 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
63
63
  # the valid signed id can't find a record.
64
64
  #
65
- # === Examples
65
+ # ==== Examples
66
66
  #
67
67
  # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
68
68
  #
@@ -76,8 +76,9 @@ module ActiveRecord
76
76
  end
77
77
 
78
78
  # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
79
- # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
80
- # 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.
81
82
  def signed_id_verifier
82
83
  @signed_id_verifier ||= begin
83
84
  secret = signed_id_verifier_secret
@@ -93,7 +94,7 @@ module ActiveRecord
93
94
 
94
95
  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
95
96
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
96
- # 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.
97
98
  def signed_id_verifier=(verifier)
98
99
  @signed_id_verifier = verifier
99
100
  end
@@ -132,6 +132,13 @@ module ActiveRecord
132
132
  tempfile = Tempfile.open("uncommented_structure.sql")
133
133
  begin
134
134
  File.foreach(filename) do |line|
135
+ next if line.start_with?("\\restrict ")
136
+
137
+ if line.start_with?("\\unrestrict ")
138
+ removing_comments = true
139
+ next
140
+ end
141
+
135
142
  unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
136
143
  tempfile << line
137
144
  removing_comments = false
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  scope: [:kind, :name]
14
14
  end
15
15
 
16
- attr_accessor :_new_record_before_last_commit # :nodoc:
16
+ attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:
17
17
 
18
18
  # = Active Record \Transactions
19
19
  #
@@ -418,6 +418,7 @@ module ActiveRecord
418
418
  status = yield
419
419
  raise ActiveRecord::Rollback unless status
420
420
  end
421
+ @_last_transaction_return_status = status
421
422
  status
422
423
  end
423
424
  end
@@ -433,6 +434,7 @@ module ActiveRecord
433
434
  def init_internals
434
435
  super
435
436
  @_start_transaction_state = nil
437
+ @_last_transaction_return_status = nil
436
438
  @_committed_already_called = nil
437
439
  @_new_record_before_last_commit = nil
438
440
  end
data/lib/active_record.rb CHANGED
@@ -86,7 +86,6 @@ module ActiveRecord
86
86
  autoload :Timestamp
87
87
  autoload :TokenFor
88
88
  autoload :TouchLater
89
- autoload :Transaction
90
89
  autoload :Transactions
91
90
  autoload :Translation
92
91
  autoload :Validations
@@ -108,6 +107,7 @@ module ActiveRecord
108
107
  autoload :Result
109
108
  autoload :StatementCache
110
109
  autoload :TableMetadata
110
+ autoload :Transaction
111
111
  autoload :Type
112
112
 
113
113
  autoload_under "relation" do
@@ -13,7 +13,7 @@ module Arel # :nodoc: all
13
13
  self
14
14
  end
15
15
 
16
- def add_bind(bind)
16
+ def add_bind(bind, &block)
17
17
  @binds << bind
18
18
  self
19
19
  end
data/lib/arel/crud.rb CHANGED
@@ -26,6 +26,7 @@ module Arel # :nodoc: all
26
26
  um.offset(offset)
27
27
  um.order(*orders)
28
28
  um.wheres = constraints
29
+ um.comment(comment)
29
30
  um.key = key
30
31
 
31
32
  um.group(group_values_columns) unless group_values_columns.empty?
@@ -39,6 +40,7 @@ module Arel # :nodoc: all
39
40
  dm.offset(offset)
40
41
  dm.order(*orders)
41
42
  dm.wheres = constraints
43
+ dm.comment(comment)
42
44
  dm.key = key
43
45
  dm.group(group_values_columns) unless group_values_columns.empty?
44
46
  dm.having(having_clause) unless having_clause.nil?
@@ -28,5 +28,10 @@ module Arel # :nodoc: all
28
28
  @ast.havings << expr
29
29
  self
30
30
  end
31
+
32
+ def comment(value)
33
+ @ast.comment = value
34
+ self
35
+ end
31
36
  end
32
37
  end