activerecord 7.0.3.1 → 7.0.4.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -0
  3. data/lib/active_record/associations/collection_association.rb +0 -1
  4. data/lib/active_record/associations/collection_proxy.rb +1 -1
  5. data/lib/active_record/associations/has_many_association.rb +7 -4
  6. data/lib/active_record/associations/join_dependency.rb +17 -13
  7. data/lib/active_record/associations.rb +6 -6
  8. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  9. data/lib/active_record/coders/yaml_column.rb +11 -5
  10. data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -1
  11. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +5 -1
  12. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  13. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  14. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -0
  15. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -1
  16. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -1
  18. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +7 -2
  19. data/lib/active_record/delegated_type.rb +1 -1
  20. data/lib/active_record/encryption/message.rb +1 -1
  21. data/lib/active_record/encryption/properties.rb +1 -1
  22. data/lib/active_record/gem_version.rb +1 -1
  23. data/lib/active_record/middleware/database_selector.rb +1 -1
  24. data/lib/active_record/model_schema.rb +21 -9
  25. data/lib/active_record/query_logs.rb +12 -1
  26. data/lib/active_record/railtie.rb +6 -34
  27. data/lib/active_record/railties/databases.rake +15 -10
  28. data/lib/active_record/relation/query_methods.rb +21 -5
  29. data/lib/active_record/relation.rb +1 -0
  30. data/lib/active_record/scoping/default.rb +4 -2
  31. data/lib/active_record/signed_id.rb +1 -1
  32. data/lib/active_record/store.rb +7 -2
  33. data/lib/active_record/test_fixtures.rb +9 -5
  34. data/lib/active_record.rb +9 -1
  35. metadata +10 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f2f45fa92947b78ba64cfc800a729cfcab5b893255efef679b686a37c50cd00
4
- data.tar.gz: 2bcca713bf8426cf4ffd22e1b832cde62a6cd6caad0f1f9b19996916e88922d5
3
+ metadata.gz: 3a5c0c6f9ce9d898d0ec937ec3d4c6ea37ae02637d8aee9cf219ac5acdec5236
4
+ data.tar.gz: 2d19932b94835dcbd398676c2528177c11f9437edc7068f44f314449ccb5abe0
5
5
  SHA512:
6
- metadata.gz: 17fdc443b9f36e8dcba05e56b6c07e88e97266f7c12accb8ac084b37dbf2c134a696fc57b71171fc488ed10fc72fba952672960b7507d14762a1a41f25623df1
7
- data.tar.gz: 4dc59bd725e08e3cdf63d5145f2038f6e5c65b5eb40e611a18ef422c516543aa2acfbc893b68f25f39d304111bafd70a131928c4c1e0692e4274e5fdb1567e53
6
+ metadata.gz: f3746715cb1a4b90ed3ce96f4826006657d450215af9f96179e53ad32b967dbad06d0328e6c18e0eedf3a576fe5efedc9c546c07149801b90c4e5eb537a3962c
7
+ data.tar.gz: a5cf64d8fbaf1023b35d22865468a824ae13a3c243b3c1084bbfafcad432a84df322346b9143a6e98273202da674bf0d3e521d903ad450209fc014998127e223
data/CHANGELOG.md CHANGED
@@ -1,3 +1,131 @@
1
+ ## Rails 7.0.4.1 (January 17, 2023) ##
2
+
3
+ * Make sanitize_as_sql_comment more strict
4
+
5
+ Though this method was likely never meant to take user input, it was
6
+ attempting sanitization. That sanitization could be bypassed with
7
+ carefully crafted input.
8
+
9
+ This commit makes the sanitization more robust by replacing any
10
+ occurrances of "/*" or "*/" with "/ *" or "* /". It also performs a
11
+ first pass to remove one surrounding comment to avoid compatibility
12
+ issues for users relying on the existing removal.
13
+
14
+ This also clarifies in the documentation of annotate that it should not
15
+ be provided user input.
16
+
17
+ [CVE-2023-22794]
18
+
19
+ * Added integer width check to PostgreSQL::Quoting
20
+
21
+ Given a value outside the range for a 64bit signed integer type
22
+ PostgreSQL will treat the column type as numeric. Comparing
23
+ integer values against numeric values can result in a slow
24
+ sequential scan.
25
+
26
+ This behavior is configurable via
27
+ ActiveRecord::Base.raise_int_wider_than_64bit which defaults to true.
28
+
29
+ [CVE-2022-44566]
30
+
31
+
32
+ ## Rails 7.0.4 (September 09, 2022) ##
33
+
34
+ * Symbol is allowed by default for YAML columns
35
+
36
+ *Étienne Barrié*
37
+
38
+ * Fix `ActiveRecord::Store` to serialize as a regular Hash
39
+
40
+ Previously it would serialize as an `ActiveSupport::HashWithIndifferentAccess`
41
+ which is wasteful and cause problem with YAML safe_load.
42
+
43
+ *Jean Boussier*
44
+
45
+ * Add `timestamptz` as a time zone aware type for PostgreSQL
46
+
47
+ This is required for correctly parsing `timestamp with time zone` values in your database.
48
+
49
+ If you don't want this, you can opt out by adding this initializer:
50
+
51
+ ```ruby
52
+ ActiveRecord::Base.time_zone_aware_types -= [:timestamptz]
53
+ ```
54
+
55
+ *Alex Ghiculescu*
56
+
57
+ * Fix supporting timezone awareness for `tsrange` and `tstzrange` array columns.
58
+
59
+ ```ruby
60
+ # In database migrations
61
+ add_column :shops, :open_hours, :tsrange, array: true
62
+ # In app config
63
+ ActiveRecord::Base.time_zone_aware_types += [:tsrange]
64
+ # In the code times are properly converted to app time zone
65
+ Shop.create!(open_hours: [Time.current..8.hour.from_now])
66
+ ```
67
+
68
+ *Wojciech Wnętrzak*
69
+
70
+ * Resolve issue where a relation cache_version could be left stale.
71
+
72
+ Previously, when `reset` was called on a relation object it did not reset the cache_versions
73
+ ivar. This led to a confusing situation where despite having the correct data the relation
74
+ still reported a stale cache_version.
75
+
76
+ Usage:
77
+
78
+ ```ruby
79
+ developers = Developer.all
80
+ developers.cache_version
81
+
82
+ Developer.update_all(updated_at: Time.now.utc + 1.second)
83
+
84
+ developers.cache_version # Stale cache_version
85
+ developers.reset
86
+ developers.cache_version # Returns the current correct cache_version
87
+ ```
88
+
89
+ Fixes #45341.
90
+
91
+ *Austen Madden*
92
+
93
+ * Fix `load_async` when called on an association proxy.
94
+
95
+ Calling `load_async` directly an association would schedule
96
+ a query but never use it.
97
+
98
+ ```ruby
99
+ comments = post.comments.load_async # schedule a query
100
+ comments.to_a # perform an entirely new sync query
101
+ ```
102
+
103
+ Now it does use the async query, however note that it doesn't
104
+ cause the association to be loaded.
105
+
106
+ *Jean Boussier*
107
+
108
+ * Fix eager loading for models without primary keys.
109
+
110
+ *Anmol Chopra*, *Matt Lawrence*, and *Jonathan Hefner*
111
+
112
+ * `rails db:schema:{dump,load}` now checks `ENV["SCHEMA_FORMAT"]` before config
113
+
114
+ Since `rails db:structure:{dump,load}` was deprecated there wasn't a simple
115
+ way to dump a schema to both SQL and Ruby formats. You can now do this with
116
+ an environment variable. For example:
117
+
118
+ ```
119
+ SCHEMA_FORMAT=sql rake db:schema:dump
120
+ ```
121
+
122
+ *Alex Ghiculescu*
123
+
124
+ * Fix Hstore deserialize regression.
125
+
126
+ *edsharp*
127
+
128
+
1
129
  ## Rails 7.0.3.1 (July 12, 2022) ##
2
130
 
3
131
  * Change ActiveRecord::Coders::YAMLColumn default to safe_load
@@ -320,7 +320,6 @@ module ActiveRecord
320
320
  # * Otherwise, attributes should have the value found in the database
321
321
  def merge_target_lists(persisted, memory)
322
322
  return persisted if memory.empty?
323
- return memory if persisted.empty?
324
323
 
325
324
  persisted.map! do |record|
326
325
  if mem_record = memory.delete(record)
@@ -1108,7 +1108,7 @@ module ActiveRecord
1108
1108
  ].flat_map { |klass|
1109
1109
  klass.public_instance_methods(false)
1110
1110
  } - self.public_instance_methods(false) - [:select] + [
1111
- :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
1111
+ :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async
1112
1112
  ]
1113
1113
 
1114
1114
  delegate(*delegate_methods, to: :scope)
@@ -79,10 +79,13 @@ module ActiveRecord
79
79
  scope.count(:all)
80
80
  end
81
81
 
82
- # If there's nothing in the database and @target has no new records
83
- # we are certain the current target is an empty array. This is a
84
- # documented side-effect of the method that may avoid an extra SELECT.
85
- loaded! if count == 0
82
+ # If there's nothing in the database, @target should only contain new
83
+ # records or be an empty array. This is a documented side-effect of
84
+ # the method that may avoid an extra SELECT.
85
+ if count == 0
86
+ target.select!(&:new_record?)
87
+ loaded!
88
+ end
86
89
 
87
90
  [association_scope.limit_value, count].compact.min
88
91
  end
@@ -252,35 +252,39 @@ module ActiveRecord
252
252
  next
253
253
  end
254
254
 
255
- key = aliases.column_alias(node, node.primary_key)
256
- id = row[key]
257
- if id.nil?
255
+ if node.primary_key
256
+ key = aliases.column_alias(node, node.primary_key)
257
+ id = row[key]
258
+ else
259
+ key = aliases.column_alias(node, node.reflection.join_primary_key.to_s)
260
+ id = nil # Avoid id-based model caching.
261
+ end
262
+
263
+ if row[key].nil?
258
264
  nil_association = ar_parent.association(node.reflection.name)
259
265
  nil_association.loaded!
260
266
  next
261
267
  end
262
268
 
263
- model = seen[ar_parent][node][id]
264
-
265
- if model
266
- construct(model, node, row, seen, model_cache, strict_loading_value)
267
- else
269
+ unless model = seen[ar_parent][node][id]
268
270
  model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
269
-
270
- seen[ar_parent][node][id] = model
271
- construct(model, node, row, seen, model_cache, strict_loading_value)
271
+ seen[ar_parent][node][id] = model if id
272
272
  end
273
+
274
+ construct(model, node, row, seen, model_cache, strict_loading_value)
273
275
  end
274
276
  end
275
277
 
276
278
  def construct_model(record, node, row, model_cache, id, strict_loading_value)
277
279
  other = record.association(node.reflection.name)
278
280
 
279
- model = model_cache[node][id] ||=
280
- node.instantiate(row, aliases.column_aliases(node)) do |m|
281
+ unless model = model_cache[node][id]
282
+ model = node.instantiate(row, aliases.column_aliases(node)) do |m|
281
283
  m.strict_loading! if strict_loading_value
282
284
  other.set_inverse_instance(m)
283
285
  end
286
+ model_cache[node][id] = model if id
287
+ end
284
288
 
285
289
  if node.reflection.collection?
286
290
  other.target.push(model)
@@ -628,15 +628,15 @@ module ActiveRecord
628
628
  #
629
629
  # Note: To trigger remove callbacks, you must use +destroy+ / +destroy_all+ methods. For example:
630
630
  #
631
- # * <tt>firm.clients.destroy(client)</tt>
632
- # * <tt>firm.clients.destroy(*clients)</tt>
633
- # * <tt>firm.clients.destroy_all</tt>
631
+ # * <tt>firm.clients.destroy(client)</tt>
632
+ # * <tt>firm.clients.destroy(*clients)</tt>
633
+ # * <tt>firm.clients.destroy_all</tt>
634
634
  #
635
635
  # +delete+ / +delete_all+ methods like the following do *not* trigger remove callbacks:
636
636
  #
637
- # * <tt>firm.clients.delete(client)</tt>
638
- # * <tt>firm.clients.delete(*clients)</tt>
639
- # * <tt>firm.clients.delete_all</tt>
637
+ # * <tt>firm.clients.delete(client)</tt>
638
+ # * <tt>firm.clients.delete(*clients)</tt>
639
+ # * <tt>firm.clients.delete_all</tt>
640
640
  #
641
641
  # == Association extensions
642
642
  #
@@ -19,6 +19,8 @@ module ActiveRecord
19
19
 
20
20
  if value.is_a?(Hash)
21
21
  set_time_zone_without_conversion(super)
22
+ elsif value.is_a?(Range)
23
+ Range.new(user_input_in_time_zone(value.begin), user_input_in_time_zone(value.end), value.exclude_end?)
22
24
  elsif value.respond_to?(:in_time_zone)
23
25
  begin
24
26
  super(user_input_in_time_zone(value)) || super
@@ -40,6 +42,8 @@ module ActiveRecord
40
42
  value.in_time_zone
41
43
  elsif value.respond_to?(:infinite?) && value.infinite?
42
44
  value
45
+ elsif value.is_a?(Range)
46
+ Range.new(convert_time_to_time_zone(value.begin), convert_time_to_time_zone(value.end), value.exclude_end?)
43
47
  else
44
48
  map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
45
49
  end
@@ -45,14 +45,20 @@ module ActiveRecord
45
45
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
46
  end
47
47
 
48
- def yaml_load(payload)
49
- if !ActiveRecord.use_yaml_unsafe_load
50
- YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
51
- else
52
- if YAML.respond_to?(:unsafe_load)
48
+ if YAML.respond_to?(:unsafe_load)
49
+ def yaml_load(payload)
50
+ if ActiveRecord.use_yaml_unsafe_load
53
51
  YAML.unsafe_load(payload)
54
52
  else
53
+ YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
54
+ end
55
+ end
56
+ else
57
+ def yaml_load(payload)
58
+ if ActiveRecord.use_yaml_unsafe_load
55
59
  YAML.load(payload)
60
+ else
61
+ YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
56
62
  end
57
63
  end
58
64
  end
@@ -146,7 +146,16 @@ module ActiveRecord
146
146
  end
147
147
 
148
148
  def sanitize_as_sql_comment(value) # :nodoc:
149
- value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
149
+ # Sanitize a string to appear within a SQL comment
150
+ # For compatibility, this also surrounding "/*+", "/*", and "*/"
151
+ # charcacters, possibly with single surrounding space.
152
+ # Then follows that by replacing any internal "*/" or "/ *" with
153
+ # "* /" or "/ *"
154
+ comment = value.to_s.dup
155
+ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
156
+ comment.gsub!("*/", "* /")
157
+ comment.gsub!("/*", "/ *")
158
+ comment
150
159
  end
151
160
 
152
161
  def column_name_matcher # :nodoc:
@@ -139,7 +139,7 @@ module ActiveRecord
139
139
  end
140
140
 
141
141
  def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse])
142
+ field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
143
143
  Arel::Nodes::Descending.new(field)
144
144
  end
145
145
 
@@ -711,6 +711,10 @@ module ActiveRecord
711
711
  options[:comment] = column.comment
712
712
  end
713
713
 
714
+ unless options.key?(:collation)
715
+ options[:collation] = column.collation
716
+ end
717
+
714
718
  unless options.key?(:auto_increment)
715
719
  options[:auto_increment] = column.auto_increment?
716
720
  end
@@ -159,7 +159,7 @@ module ActiveRecord
159
159
  end
160
160
 
161
161
  def default_type(table_name, field_name)
162
- match = create_table_info(table_name).match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/)
162
+ match = create_table_info(table_name)&.match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/)
163
163
  default_pre = match[2] if match
164
164
 
165
165
  if default_pre == "'"
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  raise(ArgumentError, ERROR % scanner.string.inspect)
27
27
  end
28
28
 
29
- unless key = scanner.scan_until(/(?<!\\)(?=")/)
29
+ unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
30
30
  raise(ArgumentError, ERROR % scanner.string.inspect)
31
31
  end
32
32
 
@@ -41,7 +41,7 @@ module ActiveRecord
41
41
  raise(ArgumentError, ERROR % scanner.string.inspect)
42
42
  end
43
43
 
44
- unless value = scanner.scan_until(/(?<!\\)(?=")/)
44
+ unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
45
45
  raise(ArgumentError, ERROR % scanner.string.inspect)
46
46
  end
47
47
 
@@ -4,6 +4,12 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ class IntegerOutOf64BitRange < StandardError
8
+ def initialize(msg)
9
+ super(msg)
10
+ end
11
+ end
12
+
7
13
  # Escapes binary strings for bytea input to the database.
8
14
  def escape_bytea(value)
9
15
  @connection.escape_bytea(value) if value
@@ -16,7 +22,27 @@ module ActiveRecord
16
22
  @connection.unescape_bytea(value) if value
17
23
  end
18
24
 
25
+ def check_int_in_range(value)
26
+ if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
27
+ exception = <<~ERROR
28
+ Provided value outside of the range of a signed 64bit integer.
29
+
30
+ PostgreSQL will treat the column type in question as a numeric.
31
+ This may result in a slow sequential scan due to a comparison
32
+ being performed between an integer or bigint value and a numeric value.
33
+
34
+ To allow for this potentially unwanted behavior, set
35
+ ActiveRecord.raise_int_wider_than_64bit to false.
36
+ ERROR
37
+ raise IntegerOutOf64BitRange.new exception
38
+ end
39
+ end
40
+
19
41
  def quote(value) # :nodoc:
42
+ if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
43
+ check_int_in_range(value)
44
+ end
45
+
20
46
  case value
21
47
  when OID::Xml::Data
22
48
  "xml '#{quote_string(value.to_s)}'"
@@ -45,8 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I''; ALTER TABLE %I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
49
49
  constraint_name,
50
+ table_schema,
51
+ table_schema,
50
52
  table_name,
51
53
  constraint_name
52
54
  ) AS constraint_check
@@ -1063,5 +1063,6 @@ module ActiveRecord
1063
1063
  ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
1064
1064
  ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
1065
1065
  end
1066
+ ActiveSupport.run_load_hooks(:active_record_postgresqladapter, PostgreSQLAdapter)
1066
1067
  end
1067
1068
  end
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  WHERE name = #{quote(row['name'])} AND type = 'index'
22
22
  SQL
23
23
 
24
- /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
24
+ /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
25
25
 
26
26
  columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
27
27
  col["name"]
@@ -341,7 +341,7 @@ module ActiveRecord
341
341
  end
342
342
 
343
343
  def get_database_version # :nodoc:
344
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
344
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
345
345
  end
346
346
 
347
347
  def check_version # :nodoc:
@@ -477,8 +477,13 @@ module ActiveRecord
477
477
  options[:rename][column.name.to_sym] ||
478
478
  column.name) : column.name
479
479
 
480
+ if column.has_default?
481
+ type = lookup_cast_type_from_column(column)
482
+ default = type.deserialize(column.default)
483
+ end
484
+
480
485
  @definition.column(column_name, column.type,
481
- limit: column.limit, default: column.default,
486
+ limit: column.limit, default: default,
482
487
  precision: column.precision, scale: column.scale,
483
488
  null: column.null, collation: column.collation,
484
489
  primary_key: column_name == from_primary_key
@@ -136,7 +136,7 @@ module ActiveRecord
136
136
  # end
137
137
  # end
138
138
  #
139
- # Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
139
+ # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
140
140
  #
141
141
  # == Nested Attributes
142
142
  #
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # * An encrypted payload
8
8
  # * A list of unencrypted headers
9
9
  #
10
- # See +Encryptor#encrypt+
10
+ # See Encryptor#encrypt
11
11
  class Message
12
12
  attr_accessor :payload, :headers
13
13
 
@@ -12,7 +12,7 @@ module ActiveRecord
12
12
  #
13
13
  # message.headers.encrypted_data_key # instead of message.headers[:k]
14
14
  #
15
- # See +Properties#DEFAULT_PROPERTIES+, +Key+, +Message+
15
+ # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
16
  class Properties
17
17
  ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
18
18
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 3
12
+ TINY = 4
13
13
  PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -44,7 +44,7 @@ module ActiveRecord
44
44
  # config.active_record.database_resolver_context = MyResolver::MySession
45
45
  #
46
46
  # Note: If you are using `rails new my_app --minimal` you will need to call
47
- # `require "active_support/core_ext/integer/time"` to load the libaries
47
+ # `require "active_support/core_ext/integer/time"` to load the libraries
48
48
  # for +Time+.
49
49
  class DatabaseSelector
50
50
  def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
@@ -126,6 +126,27 @@ module ActiveRecord
126
126
  # +:immutable_string+. This setting does not affect the behavior of
127
127
  # <tt>attribute :foo, :string</tt>. Defaults to false.
128
128
 
129
+ ##
130
+ # :singleton-method: inheritance_column
131
+ # :call-seq: inheritance_column
132
+ #
133
+ # The name of the table column which stores the class name on single-table
134
+ # inheritance situations.
135
+ #
136
+ # The default inheritance column name is +type+, which means it's a
137
+ # reserved word inside Active Record. To be able to use single-table
138
+ # inheritance with another column name, or to use the column +type+ in
139
+ # your own model for something else, you can set +inheritance_column+:
140
+ #
141
+ # self.inheritance_column = 'zoink'
142
+
143
+ ##
144
+ # :singleton-method: inheritance_column=
145
+ # :call-seq: inheritance_column=(column)
146
+ #
147
+ # Defines the name of the table column which will store the class name on single-table
148
+ # inheritance situations.
149
+
129
150
  included do
130
151
  class_attribute :primary_key_prefix_type, instance_writer: false
131
152
  class_attribute :table_name_prefix, instance_writer: false, default: ""
@@ -136,15 +157,6 @@ module ActiveRecord
136
157
  class_attribute :implicit_order_column, instance_accessor: false
137
158
  class_attribute :immutable_strings_by_default, instance_accessor: false
138
159
 
139
- # Defines the name of the table column which will store the class name on single-table
140
- # inheritance situations.
141
- #
142
- # The default inheritance column name is +type+, which means it's a
143
- # reserved word inside Active Record. To be able to use single-table
144
- # inheritance with another column name, or to use the column +type+ in
145
- # your own model for something else, you can set +inheritance_column+:
146
- #
147
- # self.inheritance_column = 'zoink'
148
160
  class_attribute :inheritance_column, instance_accessor: false, default: "type"
149
161
  singleton_class.class_eval do
150
162
  alias_method :_inheritance_column=, :inheritance_column=
@@ -33,6 +33,8 @@ module ActiveRecord
33
33
  # want to add to the comment. Dynamic content can be created by setting a proc or lambda value in a hash,
34
34
  # and can reference any value stored in the +context+ object.
35
35
  #
36
+ # Escaping is performed on the string returned, however untrusted user input should not be used.
37
+ #
36
38
  # Example:
37
39
  #
38
40
  # tags = [
@@ -109,7 +111,16 @@ module ActiveRecord
109
111
  end
110
112
 
111
113
  def escape_sql_comment(content)
112
- content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
114
+ # Sanitize a string to appear within a SQL comment
115
+ # For compatibility, this also surrounding "/*+", "/*", and "*/"
116
+ # charcacters, possibly with single surrounding space.
117
+ # Then follows that by replacing any internal "*/" or "/ *" with
118
+ # "* /" or "/ *"
119
+ comment = content.to_s.dup
120
+ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
121
+ comment.gsub!("*/", "* /")
122
+ comment.gsub!("/*", "/ *")
123
+ comment
113
124
  end
114
125
 
115
126
  def tag_content
@@ -78,6 +78,12 @@ module ActiveRecord
78
78
  end
79
79
  end
80
80
 
81
+ initializer "active_record.postgresql_time_zone_aware_types" do
82
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
83
+ ActiveRecord::Base.time_zone_aware_types << :timestamptz
84
+ end
85
+ end
86
+
81
87
  initializer "active_record.logger" do
82
88
  ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
83
89
  end
@@ -94,22 +100,6 @@ module ActiveRecord
94
100
  end
95
101
  end
96
102
 
97
- initializer "active_record.database_selector" do
98
- if options = config.active_record.database_selector
99
- resolver = config.active_record.database_resolver
100
- operations = config.active_record.database_resolver_context
101
- config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
102
- end
103
- end
104
-
105
- initializer "active_record.shard_selector" do
106
- if resolver = config.active_record.shard_resolver
107
- options = config.active_record.shard_selector || {}
108
-
109
- config.app_middleware.use ActiveRecord::Middleware::ShardSelector, resolver, options
110
- end
111
- end
112
-
113
103
  initializer "Check for cache versioning support" do
114
104
  config.after_initialize do |app|
115
105
  ActiveSupport.on_load(:active_record) do
@@ -403,23 +393,5 @@ To keep using the current cache store, you can turn off cache versioning entirel
403
393
  end
404
394
  end
405
395
  end
406
-
407
- initializer "active_record.use_yaml_unsafe_load" do |app|
408
- config.after_initialize do
409
- unless app.config.active_record.use_yaml_unsafe_load.nil?
410
- ActiveRecord.use_yaml_unsafe_load =
411
- app.config.active_record.use_yaml_unsafe_load
412
- end
413
- end
414
- end
415
-
416
- initializer "active_record.yaml_column_permitted_classes" do |app|
417
- config.after_initialize do
418
- unless app.config.active_record.yaml_column_permitted_classes.nil?
419
- ActiveRecord.yaml_column_permitted_classes =
420
- app.config.active_record.yaml_column_permitted_classes
421
- end
422
- end
423
- end
424
396
  end
425
397
  end
@@ -452,30 +452,32 @@ db_namespace = namespace :db do
452
452
  end
453
453
 
454
454
  namespace :schema do
455
- desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`)"
455
+ desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
456
456
  task dump: :load_config do
457
457
  ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
458
458
  if db_config.schema_dump
459
459
  ActiveRecord::Base.establish_connection(db_config)
460
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config)
460
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
461
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
461
462
  end
462
463
  end
463
464
 
464
465
  db_namespace["schema:dump"].reenable
465
466
  end
466
467
 
467
- desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the database"
468
+ desc "Loads 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"
468
469
  task load: [:load_config, :check_protected_environments] do
469
470
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord.schema_format, ENV["SCHEMA"])
470
471
  end
471
472
 
472
473
  namespace :dump do
473
474
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
474
- desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) for #{name} database"
475
+ desc "Creates a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) for #{name} database"
475
476
  task name => :load_config do
476
477
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name)
477
478
  ActiveRecord::Base.establish_connection(db_config)
478
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config)
479
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
480
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
479
481
  db_namespace["schema:dump:#{name}"].reenable
480
482
  end
481
483
  end
@@ -483,11 +485,12 @@ db_namespace = namespace :db do
483
485
 
484
486
  namespace :load do
485
487
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
486
- desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `config.active_record.schema_format`) into the #{name} database"
488
+ desc "Loads 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 #{name} database"
487
489
  task name => [:load_config, :check_protected_environments] do
488
490
  original_db_config = ActiveRecord::Base.connection_db_config
489
491
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name)
490
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config)
492
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
493
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
491
494
  ensure
492
495
  ActiveRecord::Base.establish_connection(original_db_config) if original_db_config
493
496
  end
@@ -545,12 +548,13 @@ db_namespace = namespace :db do
545
548
  db_namespace["test:load_schema"].invoke
546
549
  end
547
550
 
548
- # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `config.active_record.schema_format`)"
551
+ # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
549
552
  task load_schema: %w(db:test:purge) do
550
553
  should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
551
554
  ActiveRecord::Schema.verbose = false
552
555
  ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config|
553
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config)
556
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
557
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
554
558
  end
555
559
  ensure
556
560
  if should_reconnect
@@ -586,7 +590,8 @@ db_namespace = namespace :db do
586
590
  should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
587
591
  ActiveRecord::Schema.verbose = false
588
592
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name)
589
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config)
593
+ schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
594
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
590
595
  ensure
591
596
  if should_reconnect
592
597
  ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)
@@ -10,10 +10,10 @@ module ActiveRecord
10
10
  module QueryMethods
11
11
  include ActiveModel::ForbiddenAttributesProtection
12
12
 
13
- # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
14
- # In this case, #where must be chained with #not to return a new relation.
13
+ # WhereChain objects act as placeholder for queries in which +where+ does not have any parameter.
14
+ # In this case, +where+ can be chained to return a new relation.
15
15
  class WhereChain
16
- def initialize(scope)
16
+ def initialize(scope) # :nodoc:
17
17
  @scope = scope
18
18
  end
19
19
 
@@ -717,12 +717,26 @@ module ActiveRecord
717
717
  # === no argument
718
718
  #
719
719
  # If no argument is passed, #where returns a new instance of WhereChain, that
720
- # can be chained with #not to return a new relation that negates the where clause.
720
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
721
+ #
722
+ # Chaining with WhereChain#not:
721
723
  #
722
724
  # User.where.not(name: "Jon")
723
725
  # # SELECT * FROM users WHERE name != 'Jon'
724
726
  #
725
- # See WhereChain for more details on #not.
727
+ # Chaining with WhereChain#associated:
728
+ #
729
+ # Post.where.associated(:author)
730
+ # # SELECT "posts".* FROM "posts"
731
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
732
+ # # WHERE "authors"."id" IS NOT NULL
733
+ #
734
+ # Chaining with WhereChain#missing:
735
+ #
736
+ # Post.where.missing(:author)
737
+ # # SELECT "posts".* FROM "posts"
738
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
739
+ # # WHERE "authors"."id" IS NULL
726
740
  #
727
741
  # === blank condition
728
742
  #
@@ -1202,6 +1216,8 @@ module ActiveRecord
1202
1216
  # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1203
1217
  #
1204
1218
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1219
+ #
1220
+ # Some escaping is performed, however untrusted user input should not be used.
1205
1221
  def annotate(*args)
1206
1222
  check_if_method_has_arguments!(__callee__, args)
1207
1223
  spawn.annotate!(*args)
@@ -712,6 +712,7 @@ module ActiveRecord
712
712
  @to_sql = @arel = @loaded = @should_eager_load = nil
713
713
  @offsets = @take = nil
714
714
  @cache_keys = nil
715
+ @cache_versions = nil
715
716
  @records = nil
716
717
  self
717
718
  end
@@ -146,11 +146,13 @@ module ActiveRecord
146
146
  end
147
147
  elsif default_scopes.any?
148
148
  evaluate_default_scope do
149
- default_scopes.inject(relation) do |default_scope, scope_obj|
149
+ default_scopes.inject(relation) do |combined_scope, scope_obj|
150
150
  if execute_scope?(all_queries, scope_obj)
151
151
  scope = scope_obj.scope.respond_to?(:to_proc) ? scope_obj.scope : scope_obj.scope.method(:call)
152
152
 
153
- default_scope.instance_exec(&scope) || default_scope
153
+ combined_scope.instance_exec(&scope) || combined_scope
154
+ else
155
+ combined_scope
154
156
  end
155
157
  end
156
158
  end
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
 
98
98
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
99
99
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
100
- # It can further more be set to expire (the default is not to expire), and scoped down with a specific purpose.
100
+ # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
101
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
102
  # record. If a purpose is set, this too must match.
103
103
  #
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  # u.color = 'green'
60
60
  # u.color_changed? # => true
61
61
  # u.color_was # => 'black'
62
- # u.color_change # => ['black', 'red']
62
+ # u.color_change # => ['black', 'green']
63
63
  #
64
64
  # # Add additional accessors to an existing store through store_accessor
65
65
  # class SuperUser < User
@@ -268,7 +268,7 @@ module ActiveRecord
268
268
  end
269
269
 
270
270
  def dump(obj)
271
- @coder.dump self.class.as_indifferent_hash(obj)
271
+ @coder.dump as_regular_hash(obj)
272
272
  end
273
273
 
274
274
  def load(yaml)
@@ -285,6 +285,11 @@ module ActiveRecord
285
285
  ActiveSupport::HashWithIndifferentAccess.new
286
286
  end
287
287
  end
288
+
289
+ private
290
+ def as_regular_hash(obj)
291
+ obj.respond_to?(:to_hash) ? obj.to_hash : {}
292
+ end
288
293
  end
289
294
  end
290
295
  end
@@ -137,7 +137,7 @@ module ActiveRecord
137
137
  @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
138
138
  spec_name = payload[:spec_name] if payload.key?(:spec_name)
139
139
  shard = payload[:shard] if payload.key?(:shard)
140
- setup_shared_connection_pool
140
+ setup_shared_connection_pool if ActiveRecord.legacy_connection_handling
141
141
 
142
142
  if spec_name
143
143
  begin
@@ -146,10 +146,14 @@ module ActiveRecord
146
146
  connection = nil
147
147
  end
148
148
 
149
- if connection && !@fixture_connections.include?(connection)
150
- connection.begin_transaction joinable: false, _lazy: false
151
- connection.pool.lock_thread = true if lock_threads
152
- @fixture_connections << connection
149
+ if connection
150
+ setup_shared_connection_pool unless ActiveRecord.legacy_connection_handling
151
+
152
+ if !@fixture_connections.include?(connection)
153
+ connection.begin_transaction joinable: false, _lazy: false
154
+ connection.pool.lock_thread = true if lock_threads
155
+ @fixture_connections << connection
156
+ end
153
157
  end
154
158
  end
155
159
  end
data/lib/active_record.rb CHANGED
@@ -347,12 +347,20 @@ module ActiveRecord
347
347
  singleton_class.attr_accessor :use_yaml_unsafe_load
348
348
  self.use_yaml_unsafe_load = false
349
349
 
350
+ ##
351
+ # :singleton-method:
352
+ # Application configurable boolean that denotes whether or not to raise
353
+ # an exception when the PostgreSQLAdapter is provided with an integer that
354
+ # is wider than signed 64bit representation
355
+ singleton_class.attr_accessor :raise_int_wider_than_64bit
356
+ self.raise_int_wider_than_64bit = true
357
+
350
358
  ##
351
359
  # :singleton-method:
352
360
  # Application configurable array that provides additional permitted classes
353
361
  # to Psych safe_load in the YAML Coder
354
362
  singleton_class.attr_accessor :yaml_column_permitted_classes
355
- self.yaml_column_permitted_classes = []
363
+ self.yaml_column_permitted_classes = [Symbol]
356
364
 
357
365
  def self.eager_load!
358
366
  super
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.3.1
4
+ version: 7.0.4.1
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: 2022-07-12 00:00:00.000000000 Z
11
+ date: 2023-01-17 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.3.1
19
+ version: 7.0.4.1
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.3.1
26
+ version: 7.0.4.1
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.3.1
33
+ version: 7.0.4.1
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.3.1
40
+ version: 7.0.4.1
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.3.1/activerecord/CHANGELOG.md
438
- documentation_uri: https://api.rubyonrails.org/v7.0.3.1/
437
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.4.1/activerecord/CHANGELOG.md
438
+ documentation_uri: https://api.rubyonrails.org/v7.0.4.1/
439
439
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
440
- source_code_uri: https://github.com/rails/rails/tree/v7.0.3.1/activerecord
440
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.4.1/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.3
460
460
  signing_key:
461
461
  specification_version: 4
462
462
  summary: Object-relational mapper framework (part of Rails).