activerecord 7.0.6 → 7.0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -0
  3. data/lib/active_record/associations/collection_association.rb +1 -1
  4. data/lib/active_record/associations/collection_proxy.rb +5 -0
  5. data/lib/active_record/associations/has_one_association.rb +0 -4
  6. data/lib/active_record/associations/singular_association.rb +5 -7
  7. data/lib/active_record/autosave_association.rb +5 -9
  8. data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -17
  9. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +8 -6
  10. data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
  11. data/lib/active_record/connection_adapters/mysql/quoting.rb +5 -2
  12. data/lib/active_record/connection_adapters/postgresql/quoting.rb +5 -2
  13. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -2
  14. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -0
  15. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  16. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -4
  17. data/lib/active_record/gem_version.rb +2 -2
  18. data/lib/active_record/migration/compatibility.rb +12 -54
  19. data/lib/active_record/migration.rb +52 -3
  20. data/lib/active_record/persistence.rb +3 -1
  21. data/lib/active_record/railties/controller_runtime.rb +3 -4
  22. data/lib/active_record/relation/predicate_builder.rb +1 -2
  23. data/lib/active_record/relation/query_attribute.rb +23 -0
  24. data/lib/active_record/relation/query_methods.rb +26 -7
  25. data/lib/active_record/table_metadata.rb +1 -1
  26. data/lib/active_record/transactions.rb +3 -3
  27. data/lib/active_record/type/serialized.rb +4 -0
  28. data/lib/arel/nodes/and.rb +4 -0
  29. metadata +13 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fc8d9f64d2ef73d31d77f9bb3ab1792606a33e541bd28a08566304fc81d5d07
4
- data.tar.gz: 7e4b61e90c14d831d3b888859ba713dd5cbb35d1554225aec1c483ee8857aee9
3
+ metadata.gz: e000d217346c1019c2f482509ab635db50acfd196f69616c86fff4146e25a555
4
+ data.tar.gz: f8c7e6d69f291a96633a58d71862cee5f7f032b58475909ee446ebad1ac40963
5
5
  SHA512:
6
- metadata.gz: faf9806425e9a75ecde61e1b84bc6eec459cd18895684200701394315b5e7bff69487a4baab67482a80baa1f5e2fcf81021781ceb81cba39f7fbc75bfe7200d4
7
- data.tar.gz: b940c37c5f10f000f37ba68eb9be3aa2ae5dabc329a16979587dd1cd75ba464976cefccc437ba9958eb444a35f9303214d7e5f0d06d37b4e45809337977d8829
6
+ metadata.gz: da9bec970adfbb45cf2cc7e63726775d33bf49ee63b852414db9a777800faf95259b1589fb7512e0401be63e4e653caf65df37af64cec72636184a6394134eb5
7
+ data.tar.gz: e99c01e13ce496d927e6c28211f3780e97c5706bc735bb1371356c55ba9c695a7509b1613d45c127a2fbabdc96cf966168ba6f7155b7679f37566cf852cc74ca
data/CHANGELOG.md CHANGED
@@ -1,3 +1,148 @@
1
+ ## Rails 7.0.8.4 (June 04, 2024) ##
2
+
3
+ * No changes.
4
+
5
+
6
+ ## Rails 7.0.8.3 (May 17, 2024) ##
7
+
8
+ * No changes.
9
+
10
+
11
+ ## Rails 7.0.8.2 (May 16, 2024) ##
12
+
13
+ * No changes.
14
+
15
+
16
+ ## Rails 7.0.8.1 (February 21, 2024) ##
17
+
18
+ * No changes.
19
+
20
+
21
+ ## Rails 7.0.8 (September 09, 2023) ##
22
+
23
+ * Fix `change_column` not setting `precision: 6` on `datetime` columns when
24
+ using 7.0+ Migrations and SQLite.
25
+
26
+ *Hartley McGuire*
27
+
28
+ * Fix unscope is not working in specific case
29
+
30
+ Before:
31
+ ```ruby
32
+ Post.where(id: 1...3).unscope(where: :id).to_sql # "SELECT `posts`.* FROM `posts` WHERE `posts`.`id` >= 1 AND `posts`.`id` < 3"
33
+
34
+ ```
35
+
36
+ After:
37
+ ```ruby
38
+ Post.where(id: 1...3).unscope(where: :id).to_sql # "SELECT `posts`.* FROM `posts`"
39
+ ```
40
+
41
+ Fixes #48094.
42
+
43
+ *Kazuya Hatanaka*
44
+
45
+ * Fix associations to a STI model including a `class_name` parameter
46
+
47
+ ```ruby
48
+ class Product < ApplicationRecord
49
+ has_many :requests, as: :requestable, class_name: "ProductRequest", dependent: :destroy
50
+ end
51
+
52
+ # STI tables
53
+ class Request < ApplicationRecord
54
+ belongs_to :requestable, polymorphic: true
55
+
56
+ validate :request_type, presence: true
57
+ end
58
+
59
+ class ProductRequest < Request
60
+ belongs_to :user
61
+ end
62
+ ```
63
+
64
+ Accessing such association would lead to:
65
+
66
+ ```
67
+ table_metadata.rb:22:in `has_column?': undefined method `key?' for nil:NilClass (NoMethodError)
68
+ ```
69
+
70
+ *Romain Filinto*
71
+
72
+ * Fix `change_table` setting datetime precision for 6.1 Migrations
73
+
74
+ *Hartley McGuire*
75
+
76
+ * Fix change_column setting datetime precision for 6.1 Migrations
77
+
78
+ *Hartley McGuire*
79
+
80
+ ## Rails 7.0.7.2 (August 22, 2023) ##
81
+
82
+ * No changes.
83
+
84
+
85
+ ## Rails 7.0.7.1 (August 22, 2023) ##
86
+
87
+ * No changes.
88
+
89
+
90
+ ## Rails 7.0.7 (August 09, 2023) ##
91
+
92
+ * Restores functionality to the missing method when using enums and fixes.
93
+
94
+ *paulreece*
95
+
96
+ * Fix `StatementCache::Substitute` with serialized type.
97
+
98
+ *ywenc*
99
+
100
+ * Fix `:db_runtime` on notification payload when application have multiple databases.
101
+
102
+ *Eileen M. Uchitelle*
103
+
104
+ * Correctly dump check constraints for MySQL 8.0.16+.
105
+
106
+ *Steve Hill*
107
+
108
+ * Fix `ActiveRecord::QueryMethods#in_order_of` to include `nil`s, to match the
109
+ behavior of `Enumerable#in_order_of`.
110
+
111
+ For example, `Post.in_order_of(:title, [nil, "foo"])` will now include posts
112
+ with `nil` titles, the same as `Post.all.to_a.in_order_of(:title, [nil, "foo"])`.
113
+
114
+ *fatkodima*
115
+
116
+ * Revert "Fix autosave associations with validations added on `:base` of the associated objects."
117
+
118
+ This change intended to remove the :base attribute from the message,
119
+ but broke many assumptions which key these errors were stored.
120
+
121
+ *zzak*
122
+
123
+ * Fix `#previously_new_record?` to return true for destroyed records.
124
+
125
+ Before, if a record was created and then destroyed, `#previously_new_record?` would return true.
126
+ Now, any UPDATE or DELETE to a record is considered a change, and will result in `#previously_new_record?`
127
+ returning false.
128
+
129
+ *Adrianna Chang*
130
+
131
+ * Revert breaking changes to `has_one` relationship deleting the old record before the new one is validated.
132
+
133
+ *zzak*
134
+
135
+ * Fix support for Active Record instances being uses in queries.
136
+
137
+ As of `7.0.5`, query arguments were deep duped to avoid mutations impacting
138
+ the query cache, but this had the adverse effect to clearing the primary key when
139
+ the query argument contained an `ActiveRecord::Base` instance.
140
+
141
+ This broke the `noticed` gem.
142
+
143
+ *Jean Boussier*
144
+
145
+
1
146
  ## Rails 7.0.6 (June 29, 2023) ##
2
147
 
3
148
  * Fix autosave associations with validations added on `:base` of the associated objects.
@@ -12,6 +157,10 @@
12
157
 
13
158
  *fatkodima*
14
159
 
160
+ * Fix assignment into an `has_one` relationship deleting the old record before the new one is validated.
161
+
162
+ *Jean Boussier*
163
+
15
164
  * Fix where on association with has_one/has_many polymorphic relations.
16
165
 
17
166
  Before:
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  def reset
80
80
  super
81
81
  @target = []
82
- @replaced_or_added_targets = Set.new
82
+ @replaced_or_added_targets = Set.new.compare_by_identity
83
83
  @association_ids = nil
84
84
  end
85
85
 
@@ -1102,6 +1102,11 @@ module ActiveRecord
1102
1102
  super
1103
1103
  end
1104
1104
 
1105
+ def pretty_print(pp) # :nodoc:
1106
+ load_target if find_from_target?
1107
+ super
1108
+ end
1109
+
1105
1110
  delegate_methods = [
1106
1111
  QueryMethods,
1107
1112
  SpawnMethods,
@@ -87,10 +87,6 @@ module ActiveRecord
87
87
  replace(record, false)
88
88
  end
89
89
 
90
- def replace_keys(record, force: false)
91
- # Has one association doesn't have foreign keys to replace.
92
- end
93
-
94
90
  def remove_target!(method)
95
91
  case method
96
92
  when :delete
@@ -54,13 +54,11 @@ module ActiveRecord
54
54
  end
55
55
 
56
56
  def _create_record(attributes, raise_error = false, &block)
57
- reflection.klass.transaction do
58
- record = build(attributes, &block)
59
- saved = record.save
60
- replace_keys(record, force: true)
61
- raise RecordInvalid.new(record) if !saved && raise_error
62
- record
63
- end
57
+ record = build_record(attributes, &block)
58
+ saved = record.save
59
+ set_new_record(record)
60
+ raise RecordInvalid.new(record) if !saved && raise_error
61
+ record
64
62
  end
65
63
  end
66
64
  end
@@ -354,15 +354,11 @@ module ActiveRecord
354
354
  end
355
355
 
356
356
  def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
357
- normalized_attribute =
358
- if indexed_attribute
359
- "#{reflection.name}[#{index}]"
360
- else
361
- reflection.name
362
- end
363
-
364
- normalized_attribute = "#{normalized_attribute}.#{attribute}" if attribute != :base
365
- normalized_attribute
357
+ if indexed_attribute
358
+ "#{reflection.name}[#{index}].#{attribute}"
359
+ else
360
+ "#{reflection.name}.#{attribute}"
361
+ end
366
362
  end
367
363
 
368
364
  # Is used as an around_save callback to check while saving a collection
@@ -71,14 +71,6 @@ module ActiveRecord
71
71
  /\A(?:[(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
72
72
  end
73
73
 
74
- def self.quoted_column_names # :nodoc:
75
- @quoted_column_names ||= {}
76
- end
77
-
78
- def self.quoted_table_names # :nodoc:
79
- @quoted_table_names ||= {}
80
- end
81
-
82
74
  def initialize(connection, logger = nil, config = {}) # :nodoc:
83
75
  super()
84
76
 
@@ -665,15 +657,6 @@ module ActiveRecord
665
657
  migration_context.current_version
666
658
  end
667
659
 
668
- def field_ordered_value(column, values) # :nodoc:
669
- node = Arel::Nodes::Case.new(column)
670
- values.each.with_index(1) do |value, order|
671
- node.when(value).then(order)
672
- end
673
-
674
- Arel::Nodes::Ascending.new(node.else(values.length + 1))
675
- end
676
-
677
660
  class << self
678
661
  private
679
662
  def initialize_type_map(m)
@@ -138,11 +138,6 @@ module ActiveRecord
138
138
  true
139
139
  end
140
140
 
141
- def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
143
- Arel::Nodes::Descending.new(field)
144
- end
145
-
146
141
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
147
142
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
148
143
  end
@@ -438,7 +433,8 @@ module ActiveRecord
438
433
  name: row["name"]
439
434
  }
440
435
  expression = row["expression"]
441
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
436
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
437
+ expression = strip_whitespace_characters(expression)
442
438
  CheckConstraintDefinition.new(table_name, expression, options)
443
439
  end
444
440
  else
@@ -619,6 +615,12 @@ module ActiveRecord
619
615
  end
620
616
 
621
617
  private
618
+ def strip_whitespace_characters(expression)
619
+ expression = expression.gsub(/\\n|\\\\/, "")
620
+ expression = expression.gsub(/\s{2,}/, " ")
621
+ expression
622
+ end
623
+
622
624
  def text_type?(type)
623
625
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
624
626
  end
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
- :desc, :describe, :set, :show, :use
23
+ :desc, :describe, :set, :show, :use, :kill
24
24
  ) # :nodoc:
25
25
  private_constant :READ_QUERY
26
26
 
@@ -6,6 +6,9 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
9
12
  def quote_bound_value(value)
10
13
  case value
11
14
  when Rational
@@ -24,11 +27,11 @@ module ActiveRecord
24
27
  end
25
28
 
26
29
  def quote_column_name(name)
27
- self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
30
+ QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
28
31
  end
29
32
 
30
33
  def quote_table_name(name)
31
- self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
34
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
32
35
  end
33
36
 
34
37
  def unquoted_true
@@ -4,6 +4,9 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
7
10
  class IntegerOutOf64BitRange < StandardError
8
11
  def initialize(msg)
9
12
  super(msg)
@@ -81,7 +84,7 @@ module ActiveRecord
81
84
  # - "schema.name".table_name
82
85
  # - "schema.name"."table.name"
83
86
  def quote_table_name(name) # :nodoc:
84
- self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
87
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
85
88
  end
86
89
 
87
90
  # Quotes schema names for use in SQL queries.
@@ -95,7 +98,7 @@ module ActiveRecord
95
98
 
96
99
  # Quotes column names for use in SQL queries.
97
100
  def quote_column_name(name) # :nodoc:
98
- self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
101
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
99
102
  end
100
103
 
101
104
  # Quote date/time values for use in SQL input.
@@ -4,6 +4,9 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  module Quoting # :nodoc:
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
7
10
  def quote_string(s)
8
11
  @connection.class.quote(s)
9
12
  end
@@ -13,11 +16,11 @@ module ActiveRecord
13
16
  end
14
17
 
15
18
  def quote_table_name(name)
16
- self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
19
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
17
20
  end
18
21
 
19
22
  def quote_column_name(name)
20
- self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
23
+ QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
21
24
  end
22
25
 
23
26
  def quoted_time(value)
@@ -4,6 +4,12 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
7
+ def change_column(column_name, type, **options)
8
+ name = column_name.to_s
9
+ @columns_hash[name] = nil
10
+ column(name, type, **options)
11
+ end
12
+
7
13
  def references(*args, **options)
8
14
  super(*args, type: :integer, **options)
9
15
  end
@@ -84,11 +84,11 @@ module ActiveRecord
84
84
  table_sql = query_value(<<-SQL, "SCHEMA")
85
85
  SELECT sql
86
86
  FROM sqlite_master
87
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
87
+ WHERE name = #{quote(table_name)} AND type = 'table'
88
88
  UNION ALL
89
89
  SELECT sql
90
90
  FROM sqlite_temp_master
91
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
91
+ WHERE name = #{quote(table_name)} AND type = 'table'
92
92
  SQL
93
93
 
94
94
  table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
@@ -287,10 +287,7 @@ module ActiveRecord
287
287
 
288
288
  def change_column(table_name, column_name, type, **options) # :nodoc:
289
289
  alter_table(table_name) do |definition|
290
- definition[column_name].instance_eval do
291
- self.type = aliased_types(type.to_s, type)
292
- self.options.merge!(options)
293
- end
290
+ definition.change_column(column_name, type, **options)
294
291
  end
295
292
  end
296
293
 
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 6
13
- PRE = nil
12
+ TINY = 8
13
+ PRE = "4"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -56,12 +56,13 @@ module ActiveRecord
56
56
  super
57
57
  end
58
58
 
59
- def create_table(table_name, **options)
60
- if block_given?
61
- super { |t| yield compatible_table_definition(t) }
62
- else
63
- super
59
+ def change_column(table_name, column_name, type, **options)
60
+ if type == :datetime
61
+ options[:precision] ||= nil
64
62
  end
63
+
64
+ type = PostgreSQLCompat.compatible_timestamp_type(type, connection)
65
+ super
65
66
  end
66
67
 
67
68
  module TableDefinition
@@ -70,6 +71,11 @@ module ActiveRecord
70
71
  super
71
72
  end
72
73
 
74
+ def change(name, type, index: nil, **options)
75
+ options[:precision] ||= nil
76
+ super
77
+ end
78
+
73
79
  def column(name, type, index: nil, **options)
74
80
  options[:precision] ||= nil
75
81
  super
@@ -81,7 +87,7 @@ module ActiveRecord
81
87
  class << t
82
88
  prepend TableDefinition
83
89
  end
84
- t
90
+ super
85
91
  end
86
92
  end
87
93
 
@@ -105,30 +111,6 @@ module ActiveRecord
105
111
  end
106
112
  end
107
113
 
108
- def create_table(table_name, **options)
109
- if block_given?
110
- super { |t| yield compatible_table_definition(t) }
111
- else
112
- super
113
- end
114
- end
115
-
116
- def change_table(table_name, **options)
117
- if block_given?
118
- super { |t| yield compatible_table_definition(t) }
119
- else
120
- super
121
- end
122
- end
123
-
124
- def create_join_table(table_1, table_2, **options)
125
- if block_given?
126
- super { |t| yield compatible_table_definition(t) }
127
- else
128
- super
129
- end
130
- end
131
-
132
114
  def add_reference(table_name, ref_name, **options)
133
115
  if connection.adapter_name == "SQLite"
134
116
  options[:type] = :integer
@@ -182,30 +164,6 @@ module ActiveRecord
182
164
  end
183
165
  end
184
166
 
185
- def create_table(table_name, **options)
186
- if block_given?
187
- super { |t| yield compatible_table_definition(t) }
188
- else
189
- super
190
- end
191
- end
192
-
193
- def change_table(table_name, **options)
194
- if block_given?
195
- super { |t| yield compatible_table_definition(t) }
196
- else
197
- super
198
- end
199
- end
200
-
201
- def create_join_table(table_1, table_2, **options)
202
- if block_given?
203
- super { |t| yield compatible_table_definition(t) }
204
- else
205
- super
206
- end
207
- end
208
-
209
167
  def add_timestamps(table_name, **options)
210
168
  options[:precision] ||= nil
211
169
  super
@@ -551,6 +551,41 @@ module ActiveRecord
551
551
 
552
552
  # This must be defined before the inherited hook, below
553
553
  class Current < Migration # :nodoc:
554
+ def create_table(table_name, **options)
555
+ if block_given?
556
+ super { |t| yield compatible_table_definition(t) }
557
+ else
558
+ super
559
+ end
560
+ end
561
+
562
+ def change_table(table_name, **options)
563
+ if block_given?
564
+ super { |t| yield compatible_table_definition(t) }
565
+ else
566
+ super
567
+ end
568
+ end
569
+
570
+ def create_join_table(table_1, table_2, **options)
571
+ if block_given?
572
+ super { |t| yield compatible_table_definition(t) }
573
+ else
574
+ super
575
+ end
576
+ end
577
+
578
+ def drop_table(table_name, **options)
579
+ if block_given?
580
+ super { |t| yield compatible_table_definition(t) }
581
+ else
582
+ super
583
+ end
584
+ end
585
+
586
+ def compatible_table_definition(t)
587
+ t
588
+ end
554
589
  end
555
590
 
556
591
  def self.inherited(subclass) # :nodoc:
@@ -916,9 +951,7 @@ module ActiveRecord
916
951
  end
917
952
 
918
953
  def method_missing(method, *arguments, &block)
919
- arg_list = arguments.map(&:inspect) * ", "
920
-
921
- say_with_time "#{method}(#{arg_list})" do
954
+ say_with_time "#{method}(#{format_arguments(arguments)})" do
922
955
  unless connection.respond_to? :revert
923
956
  unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
924
957
  arguments[0] = proper_table_name(arguments.first, table_name_options)
@@ -1026,6 +1059,22 @@ module ActiveRecord
1026
1059
  end
1027
1060
  end
1028
1061
 
1062
+ def format_arguments(arguments)
1063
+ arg_list = arguments[0...-1].map(&:inspect)
1064
+ last_arg = arguments.last
1065
+ if last_arg.is_a?(Hash)
1066
+ last_arg = last_arg.reject { |k, _v| internal_option?(k) }
1067
+ arg_list << last_arg.inspect unless last_arg.empty?
1068
+ else
1069
+ arg_list << last_arg.inspect
1070
+ end
1071
+ arg_list.join(", ")
1072
+ end
1073
+
1074
+ def internal_option?(option_name)
1075
+ option_name.start_with?("_")
1076
+ end
1077
+
1029
1078
  def command_recorder
1030
1079
  CommandRecorder.new(connection)
1031
1080
  end
@@ -564,7 +564,7 @@ module ActiveRecord
564
564
  end
565
565
 
566
566
  # Returns true if this object was just created -- that is, prior to the last
567
- # save, the object didn't exist in the database and new_record? would have
567
+ # update or delete, the object didn't exist in the database and new_record? would have
568
568
  # returned true.
569
569
  def previously_new_record?
570
570
  @previously_new_record
@@ -663,6 +663,7 @@ module ActiveRecord
663
663
  def delete
664
664
  _delete_row if persisted?
665
665
  @destroyed = true
666
+ @previously_new_record = false
666
667
  freeze
667
668
  end
668
669
 
@@ -682,6 +683,7 @@ module ActiveRecord
682
683
  true
683
684
  end
684
685
  @destroyed = true
686
+ @previously_new_record = false
685
687
  freeze
686
688
  end
687
689
 
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  end
29
29
 
30
30
  def cleanup_view_runtime
31
- if logger && logger.info? && ActiveRecord::Base.connected?
31
+ if logger && logger.info?
32
32
  db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
33
33
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
34
34
  runtime = super
@@ -42,9 +42,8 @@ module ActiveRecord
42
42
 
43
43
  def append_info_to_payload(payload)
44
44
  super
45
- if ActiveRecord::Base.connected?
46
- payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
47
- end
45
+
46
+ payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
48
47
  end
49
48
  end
50
49
  end
@@ -65,8 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def build_bind_attribute(column_name, value)
68
- type = table.type(column_name)
69
- Relation::QueryAttribute.new(column_name, type.immutable_value(value), type)
68
+ Relation::QueryAttribute.new(column_name, value, table.type(column_name))
70
69
  end
71
70
 
72
71
  def resolve_arel_attribute(table_name, column_name, &block)
@@ -5,6 +5,20 @@ require "active_model/attribute"
5
5
  module ActiveRecord
6
6
  class Relation
7
7
  class QueryAttribute < ActiveModel::Attribute # :nodoc:
8
+ def initialize(...)
9
+ super
10
+
11
+ # The query attribute value may be mutated before we actually "compile" the query.
12
+ # To avoid that if the type uses a serializer we eagerly compute the value for database
13
+ if value_before_type_cast.is_a?(StatementCache::Substitute)
14
+ # we don't need to serialize StatementCache::Substitute
15
+ elsif @type.serialized?
16
+ value_for_database
17
+ elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
+ @value_before_type_cast = @value_before_type_cast.deep_dup
19
+ end
20
+ end
21
+
8
22
  def type_cast(value)
9
23
  value
10
24
  end
@@ -35,6 +49,15 @@ module ActiveRecord
35
49
  @_unboundable
36
50
  end
37
51
 
52
+ def ==(other)
53
+ super && value_for_database == other.value_for_database
54
+ end
55
+ alias eql? ==
56
+
57
+ def hash
58
+ [self.class, name, value_for_database, type].hash
59
+ end
60
+
38
61
  private
39
62
  def infinity?(value)
40
63
  value.respond_to?(:infinite?) && value.infinite?
@@ -77,7 +77,7 @@ module ActiveRecord
77
77
  associations.each do |association|
78
78
  reflection = scope_association_reflection(association)
79
79
  @scope.joins!(association)
80
- if @scope.table_name == reflection.table_name
80
+ if reflection.options[:class_name]
81
81
  self.not(association => { reflection.association_primary_key => nil })
82
82
  else
83
83
  self.not(reflection.table_name => { reflection.association_primary_key => nil })
@@ -109,7 +109,7 @@ module ActiveRecord
109
109
  associations.each do |association|
110
110
  reflection = scope_association_reflection(association)
111
111
  @scope.left_outer_joins!(association)
112
- if @scope.table_name == reflection.table_name
112
+ if reflection.options[:class_name]
113
113
  @scope.where!(association => { reflection.association_primary_key => nil })
114
114
  else
115
115
  @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
@@ -444,13 +444,16 @@ module ActiveRecord
444
444
  self
445
445
  end
446
446
 
447
- # Allows to specify an order by a specific set of values. Depending on your
448
- # adapter this will either use a CASE statement or a built-in function.
447
+ # Allows to specify an order by a specific set of values.
449
448
  #
450
449
  # User.in_order_of(:id, [1, 5, 3])
451
450
  # # SELECT "users".* FROM "users"
452
- # # ORDER BY FIELD("users"."id", 1, 5, 3)
453
451
  # # WHERE "users"."id" IN (1, 5, 3)
452
+ # # ORDER BY CASE
453
+ # # WHEN "users"."id" = 1 THEN 1
454
+ # # WHEN "users"."id" = 5 THEN 2
455
+ # # WHEN "users"."id" = 3 THEN 3
456
+ # # END ASC
454
457
  #
455
458
  def in_order_of(column, values)
456
459
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
@@ -462,9 +465,16 @@ module ActiveRecord
462
465
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
463
466
  arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
464
467
 
468
+ where_clause =
469
+ if values.include?(nil)
470
+ arel_column.in(values.compact).or(arel_column.eq(nil))
471
+ else
472
+ arel_column.in(values)
473
+ end
474
+
465
475
  spawn
466
- .order!(connection.field_ordered_value(arel_column, values))
467
- .where!(arel_column.in(values))
476
+ .order!(build_case_for_value_position(arel_column, values))
477
+ .where!(where_clause)
468
478
  end
469
479
 
470
480
  # Replaces any existing order defined on the relation with the specified order.
@@ -1669,6 +1679,15 @@ module ActiveRecord
1669
1679
  end
1670
1680
  end
1671
1681
 
1682
+ def build_case_for_value_position(column, values)
1683
+ node = Arel::Nodes::Case.new
1684
+ values.each.with_index(1) do |value, order|
1685
+ node.when(column.eq(value)).then(order)
1686
+ end
1687
+
1688
+ Arel::Nodes::Ascending.new(node)
1689
+ end
1690
+
1672
1691
  def resolve_arel_attributes(attrs)
1673
1692
  attrs.flat_map do |attr|
1674
1693
  case attr
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def has_column?(column_name)
22
- klass&.columns_hash.key?(column_name)
22
+ klass&.columns_hash&.key?(column_name)
23
23
  end
24
24
 
25
25
  def associated_with?(table_name)
@@ -336,9 +336,9 @@ module ActiveRecord
336
336
  @_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
337
337
  end
338
338
 
339
- # Executes +method+ within a transaction and captures its return value as a
340
- # status flag. If the status is true the transaction is committed, otherwise
341
- # a ROLLBACK is issued. In any case the status flag is returned.
339
+ # Executes a block within a transaction and captures its return value as a
340
+ # status flag. If the status is true, the transaction is committed,
341
+ # otherwise a ROLLBACK is issued. In any case, the status flag is returned.
342
342
  #
343
343
  # This method is available within the context of an ActiveRecord::Base
344
344
  # instance.
@@ -55,6 +55,10 @@ module ActiveRecord
55
55
  coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
56
56
  end
57
57
 
58
+ def serialized? # :nodoc:
59
+ true
60
+ end
61
+
58
62
  private
59
63
  def default_value?(value)
60
64
  value == coder.load(nil)
@@ -18,6 +18,10 @@ module Arel # :nodoc: all
18
18
  children[1]
19
19
  end
20
20
 
21
+ def fetch_attribute(&block)
22
+ children.any? && children.all? { |child| child.fetch_attribute(&block) }
23
+ end
24
+
21
25
  def hash
22
26
  children.hash
23
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.6
4
+ version: 7.0.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-29 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.6
19
+ version: 7.0.8.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.6
26
+ version: 7.0.8.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 7.0.6
33
+ version: 7.0.8.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 7.0.6
40
+ version: 7.0.8.4
41
41
  description: Databases on Rails. Build a persistent domain model by mapping database
42
42
  tables to Ruby classes. Strong conventions for associations, validations, aggregations,
43
43
  migrations, and testing come baked-in.
@@ -434,12 +434,12 @@ licenses:
434
434
  - MIT
435
435
  metadata:
436
436
  bug_tracker_uri: https://github.com/rails/rails/issues
437
- changelog_uri: https://github.com/rails/rails/blob/v7.0.6/activerecord/CHANGELOG.md
438
- documentation_uri: https://api.rubyonrails.org/v7.0.6/
437
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.8.4/activerecord/CHANGELOG.md
438
+ documentation_uri: https://api.rubyonrails.org/v7.0.8.4/
439
439
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
440
- source_code_uri: https://github.com/rails/rails/tree/v7.0.6/activerecord
440
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.8.4/activerecord
441
441
  rubygems_mfa_required: 'true'
442
- post_install_message:
442
+ post_install_message:
443
443
  rdoc_options:
444
444
  - "--main"
445
445
  - README.rdoc
@@ -456,8 +456,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
456
456
  - !ruby/object:Gem::Version
457
457
  version: '0'
458
458
  requirements: []
459
- rubygems_version: 3.4.13
460
- signing_key:
459
+ rubygems_version: 3.3.27
460
+ signing_key:
461
461
  specification_version: 4
462
462
  summary: Object-relational mapper framework (part of Rails).
463
463
  test_files: []