activerecord 7.0.5.1 → 7.0.7

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 +112 -2
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/associations/has_one_association.rb +0 -4
  5. data/lib/active_record/associations/singular_association.rb +5 -7
  6. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1 -1
  7. data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -17
  8. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +8 -6
  9. data/lib/active_record/connection_adapters/mysql/quoting.rb +5 -2
  10. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  11. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  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_statements.rb +2 -2
  15. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  16. data/lib/active_record/gem_version.rb +2 -2
  17. data/lib/active_record/locking/optimistic.rb +32 -18
  18. data/lib/active_record/persistence.rb +7 -5
  19. data/lib/active_record/railties/controller_runtime.rb +3 -4
  20. data/lib/active_record/reflection.rb +8 -0
  21. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  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 +34 -7
  25. data/lib/active_record/result.rb +6 -4
  26. data/lib/active_record/table_metadata.rb +5 -1
  27. data/lib/active_record/transactions.rb +3 -3
  28. data/lib/active_record/type/serialized.rb +4 -0
  29. metadata +10 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 572cf430aba8f89db10099754543391f35585ddf6e696f0f07b4b4de3a8705d4
4
- data.tar.gz: caf69799eae3b7059d405b08f1d7e0b2e09d9f794cdf0ff47ac92614da04a640
3
+ metadata.gz: cbd03f9e73d8cd308ad7eef63e3ece5612be70272833ffaa9f0c9f1251a1a0b6
4
+ data.tar.gz: c3efeab424b1d87a4c977ff53c0f97f60cfa89bd6d37723d24bd682c84f7f52a
5
5
  SHA512:
6
- metadata.gz: 232404c2cf6cf6a785e32b8aac5be7c45db0e4de54c2b9aa7cf56a8b44ea87725a1b3a044ba9c3f79b3a73cc57300ac0ef9139762d769bc6bc7e71e1274c3765
7
- data.tar.gz: 3193ff9f69dddcf8233a2ea00aef1eabaaf58525fbb4653e10ac570e3f52f917f90b2aec649926c506c8bacc81454e9c2f6ed9a7554accfd3d4fd71bb5a40177
6
+ metadata.gz: 07caf53afe430ac4a57d208fd7f46cc4430749bc6ee6d8b3666c934e37cde92d3ae2553c3ab0f3c31d18ccedcf5efc89540863ba777c7cea2eefa6e3badad17a
7
+ data.tar.gz: d9fbd502b44cdf8d53b3ff6c75f58f98aec5580bc829be6bd9511da7302ec1483ec747c9871839951d7fb37a0485c44d44de24f3bbab432db2a69d23e8c1565a
data/CHANGELOG.md CHANGED
@@ -1,8 +1,118 @@
1
+ ## Rails 7.0.7 (August 09, 2023) ##
2
+
3
+ * Restores functionality to the missing method when using enums and fixes.
4
+
5
+ *paulreece*
6
+
7
+ * Fix `StatementCache::Substitute` with serialized type.
8
+
9
+ *ywenc*
10
+
11
+ * Fix `:db_runtime` on notification payload when application have multiple databases.
12
+
13
+ *Eileen M. Uchitelle*
14
+
15
+ * Correctly dump check constraints for MySQL 8.0.16+.
16
+
17
+ *Steve Hill*
18
+
19
+ * Fix `ActiveRecord::QueryMethods#in_order_of` to include `nil`s, to match the
20
+ behavior of `Enumerable#in_order_of`.
21
+
22
+ For example, `Post.in_order_of(:title, [nil, "foo"])` will now include posts
23
+ with `nil` titles, the same as `Post.all.to_a.in_order_of(:title, [nil, "foo"])`.
24
+
25
+ *fatkodima*
26
+
27
+ * Revert "Fix autosave associations with validations added on `:base` of the associated objects."
28
+
29
+ This change intended to remove the :base attribute from the message,
30
+ but broke many assumptions which key these errors were stored.
31
+
32
+ *zzak*
33
+
34
+ * Fix `#previously_new_record?` to return true for destroyed records.
35
+
36
+ Before, if a record was created and then destroyed, `#previously_new_record?` would return true.
37
+ Now, any UPDATE or DELETE to a record is considered a change, and will result in `#previously_new_record?`
38
+ returning false.
39
+
40
+ *Adrianna Chang*
41
+
42
+ * Revert breaking changes to `has_one` relationship deleting the old record before the new one is validated.
43
+
44
+ *zzak*
45
+
46
+ * Fix support for Active Record instances being uses in queries.
47
+
48
+ As of `7.0.5`, query arguments were deep duped to avoid mutations impacting
49
+ the query cache, but this had the adverse effect to clearing the primary key when
50
+ the query argument contained an `ActiveRecord::Base` instance.
51
+
52
+ This broke the `noticed` gem.
53
+
54
+ *Jean Boussier*
55
+
56
+
57
+ ## Rails 7.0.6 (June 29, 2023) ##
58
+
59
+ * Fix autosave associations with validations added on `:base` of the associated objects.
60
+
61
+ *fatkodima*
62
+
63
+ * Fix result with anonymous PostgreSQL columns of different type from json.
64
+
65
+ *Oleksandr Avoiants*
66
+
67
+ * Preserve timestamp when setting an `ActiveSupport::TimeWithZone` value to `timestamptz` attribute.
68
+
69
+ *fatkodima*
70
+
71
+ * Fix assignment into an `has_one` relationship deleting the old record before the new one is validated.
72
+
73
+ *Jean Boussier*
74
+
75
+ * Fix where on association with has_one/has_many polymorphic relations.
76
+
77
+ Before:
78
+ ```ruby
79
+ Treasure.where(price_estimates: PriceEstimate.all)
80
+ #=> SELECT (...) WHERE "treasures"."id" IN (SELECT "price_estimates"."estimate_of_id" FROM "price_estimates")
81
+ ```
82
+
83
+ Later:
84
+ ```ruby
85
+ Treasure.where(price_estimates: PriceEstimate.all)
86
+ #=> SELECT (...) WHERE "treasures"."id" IN (SELECT "price_estimates"."estimate_of_id" FROM "price_estimates" WHERE "price_estimates"."estimate_of_type" = 'Treasure')
87
+ ```
88
+
89
+ *Lázaro Nixon*
90
+
91
+ * Fix decrementing counter caches on optimistically locked record deletion
92
+
93
+ *fatkodima*
94
+
95
+ * Ensure binary-destined values have binary encoding during type cast.
96
+
97
+ *Matthew Draper*
98
+
99
+ * Preserve existing column default functions when altering table in SQLite.
100
+
101
+ *fatkodima*
102
+
103
+ * Remove table alias added when using `where.missing` or `where.associated`.
104
+
105
+ *fatkodima*
106
+
107
+ * Fix `Enumerable#in_order_of` to only flatten first level to preserve nesting.
108
+
109
+ *Miha Rekar*
110
+
111
+
1
112
  ## Rails 7.0.5.1 (June 26, 2023) ##
2
113
 
3
114
  * No changes.
4
115
 
5
-
6
116
  ## Rails 7.0.5 (May 24, 2023) ##
7
117
 
8
118
  * Type cast `#attribute_changed?` `:from` and `:to` options.
@@ -81,7 +191,7 @@
81
191
 
82
192
  *Shota Toguchi*, *Yusaku Ono*
83
193
 
84
- * Use connection from `#with_raw_connection` in `#quote_string`.
194
+ * Fix erroneous nil default precision on virtual datetime columns.
85
195
 
86
196
  Prior to this change, virtual datetime columns did not have the same
87
197
  default precision as regular datetime columns, resulting in the following
data/README.rdoc CHANGED
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
192
192
 
193
193
  $ gem install activerecord
194
194
 
195
- Source code can be downloaded as part of the Rails project on GitHub:
195
+ Source code can be downloaded as part of the \Rails project on GitHub:
196
196
 
197
197
  * https://github.com/rails/rails/tree/main/activerecord
198
198
 
@@ -210,7 +210,7 @@ API documentation is at:
210
210
 
211
211
  * https://api.rubyonrails.org
212
212
 
213
- Bug reports for the Ruby on Rails project can be filed here:
213
+ Bug reports for the Ruby on \Rails project can be filed here:
214
214
 
215
215
  * https://github.com/rails/rails/issues
216
216
 
@@ -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
@@ -264,7 +264,7 @@ module ActiveRecord
264
264
  #
265
265
  # generates:
266
266
  #
267
- # CREATE TABLE order (
267
+ # CREATE TABLE orders (
268
268
  # product_id bigint NOT NULL,
269
269
  # client_id bigint NOT NULL
270
270
  # );
@@ -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
@@ -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
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  fields.each_with_index do |fname, i|
58
58
  ftype = result.ftype i
59
59
  fmod = result.fmod i
60
- types[fname] = get_oid_type(ftype, fmod, fname)
60
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
61
61
  end
62
62
  build_result(columns: fields, rows: result.values, column_types: types)
63
63
  end
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  return if value.blank?
14
14
 
15
15
  time = super
16
- return time unless time.acts_like?(:time)
16
+ return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
17
17
 
18
18
  # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
19
19
  # We prefer times always in UTC, so here we convert back.
@@ -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)
@@ -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|
@@ -480,6 +480,7 @@ module ActiveRecord
480
480
  if column.has_default?
481
481
  type = lookup_cast_type_from_column(column)
482
482
  default = type.deserialize(column.default)
483
+ default = -> { column.default_function } if default.nil?
483
484
  end
484
485
 
485
486
  @definition.column(column_name, column.type,
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 5
13
- PRE = "1"
12
+ TINY = 7
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Locking
5
- # == What is Optimistic Locking
5
+ # == What is \Optimistic \Locking
6
6
  #
7
7
  # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
8
8
  # conflicts with the data. It does this by checking whether another process has made changes to a record since
9
- # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
9
+ # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
10
10
  # and the update is ignored.
11
11
  #
12
- # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
12
+ # Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
13
13
  #
14
14
  # == Usage
15
15
  #
@@ -69,6 +69,11 @@ module ActiveRecord
69
69
  end
70
70
  end
71
71
 
72
+ def initialize_dup(other) # :nodoc:
73
+ super
74
+ _clear_locking_column if locking_enabled?
75
+ end
76
+
72
77
  private
73
78
  def _create_record(attribute_names = self.attribute_names)
74
79
  if locking_enabled?
@@ -91,8 +96,7 @@ module ActiveRecord
91
96
  locking_column = self.class.locking_column
92
97
  lock_attribute_was = @attributes[locking_column]
93
98
 
94
- update_constraints = _primary_key_constraints_hash
95
- update_constraints[locking_column] = _lock_value_for_database(locking_column)
99
+ update_constraints = _query_constraints_hash
96
100
 
97
101
  attribute_names = attribute_names.dup if attribute_names.frozen?
98
102
  attribute_names << locking_column
@@ -118,16 +122,9 @@ module ActiveRecord
118
122
  end
119
123
 
120
124
  def destroy_row
121
- return super unless locking_enabled?
122
-
123
- locking_column = self.class.locking_column
125
+ affected_rows = super
124
126
 
125
- delete_constraints = _primary_key_constraints_hash
126
- delete_constraints[locking_column] = _lock_value_for_database(locking_column)
127
-
128
- affected_rows = self.class._delete_record(delete_constraints)
129
-
130
- if affected_rows != 1
127
+ if locking_enabled? && affected_rows != 1
131
128
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
132
129
  end
133
130
 
@@ -142,6 +139,18 @@ module ActiveRecord
142
139
  end
143
140
  end
144
141
 
142
+ def _clear_locking_column
143
+ self[self.class.locking_column] = nil
144
+ clear_attribute_change(self.class.locking_column)
145
+ end
146
+
147
+ def _query_constraints_hash
148
+ return super unless locking_enabled?
149
+
150
+ locking_column = self.class.locking_column
151
+ super.merge(locking_column => _lock_value_for_database(locking_column))
152
+ end
153
+
145
154
  module ClassMethods
146
155
  DEFAULT_LOCKING_COLUMN = "lock_version"
147
156
 
@@ -159,10 +168,7 @@ module ActiveRecord
159
168
  end
160
169
 
161
170
  # The version column used for optimistic locking. Defaults to +lock_version+.
162
- def locking_column
163
- @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
164
- @locking_column
165
- end
171
+ attr_reader :locking_column
166
172
 
167
173
  # Reset the column used for optimistic locking back to the +lock_version+ default.
168
174
  def reset_locking_column
@@ -182,6 +188,14 @@ module ActiveRecord
182
188
  end
183
189
  super
184
190
  end
191
+
192
+ private
193
+ def inherited(base)
194
+ super
195
+ base.class_eval do
196
+ @locking_column = DEFAULT_LOCKING_COLUMN
197
+ end
198
+ end
185
199
  end
186
200
  end
187
201
 
@@ -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
 
@@ -813,7 +815,7 @@ module ActiveRecord
813
815
  verify_readonly_attribute(name) || name
814
816
  end
815
817
 
816
- update_constraints = _primary_key_constraints_hash
818
+ update_constraints = _query_constraints_hash
817
819
  attributes = attributes.each_with_object({}) do |(k, v), h|
818
820
  h[k] = @attributes.write_cast_value(k, v)
819
821
  clear_attribute_change(k)
@@ -1028,7 +1030,7 @@ module ActiveRecord
1028
1030
  (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
1029
1031
  end
1030
1032
 
1031
- def _primary_key_constraints_hash
1033
+ def _query_constraints_hash
1032
1034
  { @primary_key => id_in_database }
1033
1035
  end
1034
1036
 
@@ -1041,7 +1043,7 @@ module ActiveRecord
1041
1043
  end
1042
1044
 
1043
1045
  def _delete_row
1044
- self.class._delete_record(_primary_key_constraints_hash)
1046
+ self.class._delete_record(_query_constraints_hash)
1045
1047
  end
1046
1048
 
1047
1049
  def _touch_row(attribute_names, time)
@@ -1057,7 +1059,7 @@ module ActiveRecord
1057
1059
  def _update_row(attribute_names, attempted_action = "update")
1058
1060
  self.class._update_record(
1059
1061
  attributes_with_values(attribute_names),
1060
- _primary_key_constraints_hash
1062
+ _query_constraints_hash
1061
1063
  )
1062
1064
  end
1063
1065
 
@@ -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
@@ -485,6 +485,10 @@ module ActiveRecord
485
485
  foreign_key
486
486
  end
487
487
 
488
+ def join_primary_type
489
+ type
490
+ end
491
+
488
492
  def join_foreign_key
489
493
  active_record_primary_key
490
494
  end
@@ -588,6 +592,10 @@ module ActiveRecord
588
592
  options[:polymorphic]
589
593
  end
590
594
 
595
+ def polymorphic_name
596
+ active_record.polymorphic_name
597
+ end
598
+
591
599
  def add_as_source(seed)
592
600
  seed
593
601
  end
@@ -18,7 +18,10 @@ module ActiveRecord
18
18
  def ids
19
19
  case value
20
20
  when Relation
21
- value.select_values.empty? ? value.select(primary_key) : value
21
+ relation = value
22
+ relation = relation.select(primary_key) if select_clause?
23
+ relation = relation.where(primary_type => polymorphic_name) if polymorphic_clause?
24
+ relation
22
25
  when Array
23
26
  value.map { |v| convert_to_id(v) }
24
27
  else
@@ -30,6 +33,22 @@ module ActiveRecord
30
33
  associated_table.join_primary_key
31
34
  end
32
35
 
36
+ def primary_type
37
+ associated_table.join_primary_type
38
+ end
39
+
40
+ def polymorphic_name
41
+ associated_table.polymorphic_name_association
42
+ end
43
+
44
+ def select_clause?
45
+ value.select_values.empty?
46
+ end
47
+
48
+ def polymorphic_clause?
49
+ primary_type && !value.where_values_hash.has_key?(primary_type)
50
+ end
51
+
33
52
  def convert_to_id(value)
34
53
  if value.respond_to?(primary_key)
35
54
  value.public_send(primary_key)
@@ -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,11 @@ module ActiveRecord
77
77
  associations.each do |association|
78
78
  reflection = scope_association_reflection(association)
79
79
  @scope.joins!(association)
80
- self.not(association => { reflection.association_primary_key => nil })
80
+ if reflection.options[:class_name]
81
+ self.not(association => { reflection.association_primary_key => nil })
82
+ else
83
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
84
+ end
81
85
  end
82
86
 
83
87
  @scope
@@ -105,7 +109,11 @@ module ActiveRecord
105
109
  associations.each do |association|
106
110
  reflection = scope_association_reflection(association)
107
111
  @scope.left_outer_joins!(association)
108
- @scope.where!(association => { reflection.association_primary_key => nil })
112
+ if reflection.options[:class_name]
113
+ @scope.where!(association => { reflection.association_primary_key => nil })
114
+ else
115
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
116
+ end
109
117
  end
110
118
 
111
119
  @scope
@@ -436,13 +444,16 @@ module ActiveRecord
436
444
  self
437
445
  end
438
446
 
439
- # Allows to specify an order by a specific set of values. Depending on your
440
- # 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.
441
448
  #
442
449
  # User.in_order_of(:id, [1, 5, 3])
443
450
  # # SELECT "users".* FROM "users"
444
- # # ORDER BY FIELD("users"."id", 1, 5, 3)
445
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
446
457
  #
447
458
  def in_order_of(column, values)
448
459
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
@@ -454,9 +465,16 @@ module ActiveRecord
454
465
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
455
466
  arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
456
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
+
457
475
  spawn
458
- .order!(connection.field_ordered_value(arel_column, values))
459
- .where!(arel_column.in(values))
476
+ .order!(build_case_for_value_position(arel_column, values))
477
+ .where!(where_clause)
460
478
  end
461
479
 
462
480
  # Replaces any existing order defined on the relation with the specified order.
@@ -1661,6 +1679,15 @@ module ActiveRecord
1661
1679
  end
1662
1680
  end
1663
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
+
1664
1691
  def resolve_arel_attributes(attrs)
1665
1692
  attrs.flat_map do |attr|
1666
1693
  case attr
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
  type = if type_overrides.is_a?(Array)
109
109
  type_overrides.first
110
110
  else
111
- column_type(columns.first, type_overrides)
111
+ column_type(columns.first, 0, type_overrides)
112
112
  end
113
113
 
114
114
  rows.map do |(value)|
@@ -118,7 +118,7 @@ module ActiveRecord
118
118
  types = if type_overrides.is_a?(Array)
119
119
  type_overrides
120
120
  else
121
- columns.map { |name| column_type(name, type_overrides) }
121
+ columns.map.with_index { |name, i| column_type(name, i, type_overrides) }
122
122
  end
123
123
 
124
124
  rows.map do |values|
@@ -135,9 +135,11 @@ module ActiveRecord
135
135
  end
136
136
 
137
137
  private
138
- def column_type(name, type_overrides = {})
138
+ def column_type(name, index, type_overrides)
139
139
  type_overrides.fetch(name) do
140
- column_types.fetch(name, Type.default_value)
140
+ column_types.fetch(index) do
141
+ column_types.fetch(name, Type.default_value)
142
+ end
141
143
  end
142
144
  end
143
145
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
- delegate :join_primary_key, :join_foreign_key, :join_foreign_type, to: :reflection
5
+ delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
6
6
 
7
7
  def initialize(klass, arel_table, reflection = nil)
8
8
  @klass = klass
@@ -54,6 +54,10 @@ module ActiveRecord
54
54
  reflection&.polymorphic?
55
55
  end
56
56
 
57
+ def polymorphic_name_association
58
+ reflection&.polymorphic_name
59
+ end
60
+
57
61
  def through_association?
58
62
  reflection&.through_reflection?
59
63
  end
@@ -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)
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.5.1
4
+ version: 7.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-26 00:00:00.000000000 Z
11
+ date: 2023-08-09 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.5.1
19
+ version: 7.0.7
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.5.1
26
+ version: 7.0.7
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.5.1
33
+ version: 7.0.7
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.5.1
40
+ version: 7.0.7
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,10 +434,10 @@ 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.5.1/activerecord/CHANGELOG.md
438
- documentation_uri: https://api.rubyonrails.org/v7.0.5.1/
437
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.7/activerecord/CHANGELOG.md
438
+ documentation_uri: https://api.rubyonrails.org/v7.0.7/
439
439
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
440
- source_code_uri: https://github.com/rails/rails/tree/v7.0.5.1/activerecord
440
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.7/activerecord
441
441
  rubygems_mfa_required: 'true'
442
442
  post_install_message:
443
443
  rdoc_options:
@@ -456,7 +456,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
456
456
  - !ruby/object:Gem::Version
457
457
  version: '0'
458
458
  requirements: []
459
- rubygems_version: 3.3.3
459
+ rubygems_version: 3.4.10
460
460
  signing_key:
461
461
  specification_version: 4
462
462
  summary: Object-relational mapper framework (part of Rails).