activerecord 6.0.3.7 → 6.0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +2 -3
  5. data/lib/active_record/associations/association_scope.rb +7 -1
  6. data/lib/active_record/associations/collection_association.rb +7 -0
  7. data/lib/active_record/associations/collection_proxy.rb +1 -0
  8. data/lib/active_record/associations/join_dependency.rb +14 -6
  9. data/lib/active_record/associations/join_dependency/join_association.rb +7 -0
  10. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  11. data/lib/active_record/associations/preloader.rb +6 -2
  12. data/lib/active_record/associations/preloader/association.rb +41 -23
  13. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  14. data/lib/active_record/associations/through_association.rb +1 -1
  15. data/lib/active_record/autosave_association.rb +10 -10
  16. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -0
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -1
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -0
  19. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +9 -4
  20. data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
  21. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -4
  22. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  23. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  24. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  25. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +1 -1
  26. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -1
  27. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -1
  28. data/lib/active_record/core.rb +5 -6
  29. data/lib/active_record/enum.rb +13 -6
  30. data/lib/active_record/fixture_set/render_context.rb +1 -1
  31. data/lib/active_record/gem_version.rb +2 -2
  32. data/lib/active_record/insert_all.rb +1 -1
  33. data/lib/active_record/locking/optimistic.rb +9 -0
  34. data/lib/active_record/model_schema.rb +29 -0
  35. data/lib/active_record/reflection.rb +11 -13
  36. data/lib/active_record/relation.rb +6 -3
  37. data/lib/active_record/relation/calculations.rb +1 -1
  38. data/lib/active_record/relation/delegation.rb +2 -1
  39. data/lib/active_record/relation/finder_methods.rb +1 -1
  40. data/lib/active_record/relation/merger.rb +7 -2
  41. data/lib/active_record/relation/query_methods.rb +23 -11
  42. data/lib/active_record/scoping/named.rb +5 -0
  43. data/lib/active_record/test_fixtures.rb +19 -1
  44. data/lib/active_record/type/time.rb +10 -0
  45. data/lib/active_record/validations/associated.rb +1 -1
  46. metadata +12 -12
@@ -1035,6 +1035,12 @@ module ActiveRecord
1035
1035
  # In some cases you may want to prevent writes to the database
1036
1036
  # even if you are on a database that can write. `while_preventing_writes`
1037
1037
  # will prevent writes to the database for the duration of the block.
1038
+ #
1039
+ # This method does not provide the same protection as a readonly
1040
+ # user and is meant to be a safeguard against accidental writes.
1041
+ #
1042
+ # See `READ_QUERY` for the queries that are blocked by this
1043
+ # method.
1038
1044
  def while_preventing_writes(enabled = true)
1039
1045
  original, self.prevent_writes = self.prevent_writes, enabled
1040
1046
  yield
@@ -63,6 +63,10 @@ module ActiveRecord
63
63
  end
64
64
  CODE
65
65
  end
66
+
67
+ def aliased_types(name, fallback)
68
+ "timestamp" == name ? :datetime : fallback
69
+ end
66
70
  end
67
71
 
68
72
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -105,8 +109,9 @@ module ActiveRecord
105
109
  !ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
106
110
  end
107
111
 
108
- def defined_for?(to_table: nil, **options)
112
+ def defined_for?(to_table: nil, validate: nil, **options)
109
113
  (to_table.nil? || to_table.to_s == self.to_table) &&
114
+ (validate.nil? || validate == options.fetch(:validate, validate)) &&
110
115
  options.all? { |k, v| self.options[k].to_s == v.to_s }
111
116
  end
112
117
 
@@ -103,7 +103,11 @@ module ActiveRecord
103
103
  end
104
104
  end
105
105
 
106
+ DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc:
107
+ private_constant :DEFAULT_READ_QUERY
108
+
106
109
  def self.build_read_query_regexp(*parts) # :nodoc:
110
+ parts += DEFAULT_READ_QUERY
107
111
  parts = parts.map { |part| /#{part}/i }
108
112
  /\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
109
113
  end
@@ -168,6 +172,8 @@ module ActiveRecord
168
172
  spec_name = conn.pool.spec.name
169
173
  name = "#{spec_name}::SchemaMigration"
170
174
 
175
+ return ActiveRecord::SchemaMigration if spec_name == "primary"
176
+
171
177
  Class.new(ActiveRecord::SchemaMigration) do
172
178
  define_singleton_method(:name) { name }
173
179
  define_singleton_method(:to_s) { name }
@@ -675,9 +675,10 @@ module ActiveRecord
675
675
  end
676
676
 
677
677
  def add_index_for_alter(table_name, column_name, options = {})
678
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, **options)
678
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
679
679
  index_algorithm[0, 0] = ", " if index_algorithm.present?
680
- "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
680
+ sql = +"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
681
+ add_sql_comment!(sql, comment)
681
682
  end
682
683
 
683
684
  def remove_index_for_alter(table_name, options = {})
@@ -686,7 +687,11 @@ module ActiveRecord
686
687
  end
687
688
 
688
689
  def supports_rename_index?
689
- mariadb? ? false : database_version >= "5.7.6"
690
+ if mariadb?
691
+ database_version >= "10.5.2"
692
+ else
693
+ database_version >= "5.7.6"
694
+ end
690
695
  end
691
696
 
692
697
  def configure_connection
@@ -739,7 +744,7 @@ module ActiveRecord
739
744
  end.compact.join(", ")
740
745
 
741
746
  # ...and send them all in one query
742
- execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
747
+ execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
743
748
  end
744
749
 
745
750
  def column_definitions(table_name) # :nodoc:
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
- :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc, :with
23
+ :desc, :describe, :set, :show, :use
24
24
  ) # :nodoc:
25
25
  private_constant :READ_QUERY
26
26
 
@@ -203,10 +203,14 @@ module ActiveRecord
203
203
  def data_source_sql(name = nil, type: nil)
204
204
  scope = quoted_scope(name, type: type)
205
205
 
206
- sql = +"SELECT table_name FROM information_schema.tables"
207
- sql << " WHERE table_schema = #{scope[:schema]}"
208
- sql << " AND table_name = #{scope[:name]}" if scope[:name]
209
- sql << " AND table_type = #{scope[:type]}" if scope[:type]
206
+ sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
207
+ sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
208
+ if scope[:type] || scope[:name]
209
+ conditions = []
210
+ conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
211
+ conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
212
+ sql << " WHERE #{conditions.join(" AND ")}"
213
+ end
210
214
  sql
211
215
  end
212
216
 
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  end
69
69
 
70
70
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
71
- :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :with
71
+ :close, :declare, :fetch, :move, :set, :show
72
72
  ) # :nodoc:
73
73
  private_constant :READ_QUERY
74
74
 
@@ -16,6 +16,14 @@ module ActiveRecord
16
16
  super
17
17
  end
18
18
  end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -67,15 +67,34 @@ module ActiveRecord
67
67
  end
68
68
 
69
69
  def extract_bounds(value)
70
- from, to = value[1..-2].split(",")
70
+ from, to = value[1..-2].split(",", 2)
71
71
  {
72
- from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from,
73
- to: (value[-2] == "," || to == "infinity") ? infinity : to,
74
- exclude_start: (value[0] == "("),
75
- exclude_end: (value[-1] == ")")
72
+ from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
73
+ to: (to == "" || to == "infinity") ? infinity : unquote(to),
74
+ exclude_start: value.start_with?("("),
75
+ exclude_end: value.end_with?(")")
76
76
  }
77
77
  end
78
78
 
79
+ # When formatting the bound values of range types, PostgreSQL quotes
80
+ # the bound value using double-quotes in certain conditions. Within
81
+ # a double-quoted string, literal " and \ characters are themselves
82
+ # escaped. In input, PostgreSQL accepts multiple escape styles for "
83
+ # (either \" or "") but in output always uses "".
84
+ # See:
85
+ # * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
86
+ # * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
87
+ def unquote(value)
88
+ if value.start_with?('"') && value.end_with?('"')
89
+ unquoted_value = value[1..-2]
90
+ unquoted_value.gsub!('""', '"')
91
+ unquoted_value.gsub!('\\\\', '\\')
92
+ unquoted_value
93
+ else
94
+ value
95
+ end
96
+ end
97
+
79
98
  def infinity(negative: false)
80
99
  if subtype.respond_to?(:infinity)
81
100
  subtype.infinity(negative: negative)
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  module DatabaseStatements
7
7
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
- :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
8
+ :pragma
9
9
  ) # :nodoc:
10
10
  private_constant :READ_QUERY
11
11
 
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
 
62
62
  def remove_foreign_key(from_table, to_table = nil, **options)
63
63
  to_table ||= options[:to_table]
64
- options = options.except(:name, :to_table)
64
+ options = options.except(:name, :to_table, :validate)
65
65
  foreign_keys = foreign_keys(from_table)
66
66
 
67
67
  fkey = foreign_keys.detect do |fk|
@@ -283,13 +283,14 @@ module ActiveRecord
283
283
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
284
284
  alter_table(table_name) do |definition|
285
285
  definition[column_name].instance_eval do
286
- self.type = type
286
+ self.type = aliased_types(type.to_s, type)
287
287
  self.limit = options[:limit] if options.include?(:limit)
288
288
  self.default = options[:default] if options.include?(:default)
289
289
  self.null = options[:null] if options.include?(:null)
290
290
  self.precision = options[:precision] if options.include?(:precision)
291
291
  self.scale = options[:scale] if options.include?(:scale)
292
292
  self.collation = options[:collation] if options.include?(:collation)
293
+ self.options.merge!(options)
293
294
  end
294
295
  end
295
296
  end
@@ -285,18 +285,17 @@ module ActiveRecord
285
285
  false
286
286
  end
287
287
 
288
- private
289
- def cached_find_by_statement(key, &block)
290
- cache = @find_by_statement_cache[connection.prepared_statements]
291
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
292
- end
288
+ def cached_find_by_statement(key, &block) # :nodoc:
289
+ cache = @find_by_statement_cache[connection.prepared_statements]
290
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
291
+ end
293
292
 
293
+ private
294
294
  def relation
295
295
  relation = Relation.create(self)
296
296
 
297
297
  if finder_needs_type_condition? && !ignore_default_scope?
298
298
  relation.where!(type_condition)
299
- relation.create_with!(inheritance_column.to_s => sti_name)
300
299
  else
301
300
  relation
302
301
  end
@@ -173,6 +173,7 @@ module ActiveRecord
173
173
 
174
174
  _enum_methods_module.module_eval do
175
175
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
176
+ value_method_names = []
176
177
  pairs.each do |label, value|
177
178
  if enum_prefix == true
178
179
  prefix = "#{name}_"
@@ -186,6 +187,7 @@ module ActiveRecord
186
187
  end
187
188
 
188
189
  value_method_name = "#{prefix}#{label}#{suffix}"
190
+ value_method_names << value_method_name
189
191
  enum_values[label] = value
190
192
  label = label.to_s
191
193
 
@@ -200,8 +202,6 @@ module ActiveRecord
200
202
  # scope :active, -> { where(status: 0) }
201
203
  # scope :not_active, -> { where.not(status: 0) }
202
204
  if enum_scopes != false
203
- klass.send(:detect_negative_condition!, value_method_name)
204
-
205
205
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
206
206
  klass.scope value_method_name, -> { where(attr => value) }
207
207
 
@@ -209,6 +209,7 @@ module ActiveRecord
209
209
  klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
210
210
  end
211
211
  end
212
+ klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
212
213
  end
213
214
  enum_values.freeze
214
215
  end
@@ -264,10 +265,16 @@ module ActiveRecord
264
265
  }
265
266
  end
266
267
 
267
- def detect_negative_condition!(method_name)
268
- if method_name.start_with?("not_") && logger
269
- logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
270
- " This will cause a conflict with auto generated negative scopes."
268
+ def detect_negative_enum_conditions!(method_names)
269
+ return unless logger
270
+
271
+ method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
272
+ inverted_form = potential_not.sub("not_", "")
273
+ if method_names.include?(inverted_form)
274
+ logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
275
+ " This has caused a conflict with auto generated negative scopes." \
276
+ " Avoid using enum elements starting with 'not' where the positive form is also an element."
277
+ end
271
278
  end
272
279
  end
273
280
  end
@@ -10,7 +10,7 @@ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
10
10
  end
11
11
 
12
12
  def binary(path)
13
- %(!!binary "#{Base64.strict_encode64(File.read(path))}")
13
+ %(!!binary "#{Base64.strict_encode64(File.read(path, mode: 'rb'))}")
14
14
  end
15
15
  end
16
16
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 0
12
- TINY = 3
13
- PRE = "7"
12
+ TINY = 4
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -168,7 +168,7 @@ module ActiveRecord
168
168
  end
169
169
 
170
170
  def format_columns(columns)
171
- quote_columns(columns).join(",")
171
+ columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
172
172
  end
173
173
 
174
174
  def quote_columns(columns)
@@ -60,6 +60,15 @@ module ActiveRecord
60
60
  self.class.locking_enabled?
61
61
  end
62
62
 
63
+ def increment!(*, **) #:nodoc:
64
+ super.tap do
65
+ if locking_enabled?
66
+ self[self.class.locking_column] += 1
67
+ clear_attribute_change(self.class.locking_column)
68
+ end
69
+ end
70
+ end
71
+
63
72
  private
64
73
  def _create_record(attribute_names = self.attribute_names)
65
74
  if locking_enabled?
@@ -287,6 +287,35 @@ module ActiveRecord
287
287
 
288
288
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
289
289
  # accessors defined, and won't be referenced in SQL queries.
290
+ #
291
+ # A common usage pattern for this method is to ensure all references to an attribute
292
+ # have been removed and deployed, before a migration to drop the column from the database
293
+ # has been deployed and run. Using this two step approach to dropping columns ensures there
294
+ # is no code that raises errors due to having a cached schema in memory at the time the
295
+ # schema migration is run.
296
+ #
297
+ # For example, given a model where you want to drop the "category" attribute, first mark it
298
+ # as ignored:
299
+ #
300
+ # class Project < ActiveRecord::Base
301
+ # # schema:
302
+ # # id :bigint
303
+ # # name :string, limit: 255
304
+ # # category :string, limit: 255
305
+ #
306
+ # self.ignored_columns = [:category]
307
+ # end
308
+ #
309
+ # The schema still contains `category`, but now the model omits it, so any meta-driven code or
310
+ # schema caching will not attempt to use the column:
311
+ #
312
+ # Project.columns_hash["category"] => nil
313
+ #
314
+ # You will get an error if accessing that attribute directly, so ensure all usages of the
315
+ # column are removed (automated tests can help you find any usages).
316
+ #
317
+ # user = Project.create!(name: "First Project")
318
+ # user.category # => raises NoMethodError
290
319
  def ignored_columns=(columns)
291
320
  @ignored_columns = columns.map(&:to_s)
292
321
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/filters"
4
- require "concurrent/map"
5
4
 
6
5
  module ActiveRecord
7
6
  # = Active Record Reflection
@@ -201,9 +200,9 @@ module ActiveRecord
201
200
  klass_scope
202
201
  end
203
202
 
204
- def join_scopes(table, predicate_builder) # :nodoc:
203
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
205
204
  if scope
206
- [scope_for(build_scope(table, predicate_builder))]
205
+ [scope_for(build_scope(table, predicate_builder, klass))]
207
206
  else
208
207
  []
209
208
  end
@@ -292,7 +291,7 @@ module ActiveRecord
292
291
  JoinKeys.new(join_primary_key(association_klass), join_foreign_key)
293
292
  end
294
293
 
295
- def build_scope(table, predicate_builder = predicate_builder(table))
294
+ def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
296
295
  Relation.create(
297
296
  klass,
298
297
  table: table,
@@ -432,19 +431,18 @@ module ActiveRecord
432
431
  @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
433
432
  @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type")
434
433
  @constructable = calculate_constructable(macro, options)
435
- @association_scope_cache = Concurrent::Map.new
436
434
 
437
435
  if options[:class_name] && options[:class_name].class == Class
438
436
  raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
439
437
  end
440
438
  end
441
439
 
442
- def association_scope_cache(conn, owner, &block)
443
- key = conn.prepared_statements
440
+ def association_scope_cache(klass, owner, &block)
441
+ key = self
444
442
  if polymorphic?
445
443
  key = [key, owner._read_attribute(@foreign_type)]
446
444
  end
447
- @association_scope_cache.compute_if_absent(key) { StatementCache.create(conn, &block) }
445
+ klass.cached_find_by_statement(key, &block)
448
446
  end
449
447
 
450
448
  def constructable? # :nodoc:
@@ -510,7 +508,7 @@ module ActiveRecord
510
508
  # This is for clearing cache on the reflection. Useful for tests that need to compare
511
509
  # SQL queries on associations.
512
510
  def clear_association_scope_cache # :nodoc:
513
- @association_scope_cache.clear
511
+ klass.initialize_find_by_cache
514
512
  end
515
513
 
516
514
  def nested?
@@ -839,8 +837,8 @@ module ActiveRecord
839
837
  source_reflection.scopes + super
840
838
  end
841
839
 
842
- def join_scopes(table, predicate_builder) # :nodoc:
843
- source_reflection.join_scopes(table, predicate_builder) + super
840
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
841
+ source_reflection.join_scopes(table, predicate_builder, klass) + super
844
842
  end
845
843
 
846
844
  def has_scope?
@@ -1003,9 +1001,9 @@ module ActiveRecord
1003
1001
  @previous_reflection = previous_reflection
1004
1002
  end
1005
1003
 
1006
- def join_scopes(table, predicate_builder) # :nodoc:
1004
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
1007
1005
  scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
1008
- scopes << build_scope(table, predicate_builder).instance_exec(nil, &source_type_scope)
1006
+ scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
1009
1007
  end
1010
1008
 
1011
1009
  def constraints