activerecord 7.0.3.1 → 7.0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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).