activerecord 6.1.0.rc1 → 6.1.2.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +177 -20
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +4 -4
  5. data/lib/active_record/association_relation.rb +10 -0
  6. data/lib/active_record/associations.rb +6 -2
  7. data/lib/active_record/associations/association.rb +14 -9
  8. data/lib/active_record/associations/association_scope.rb +7 -5
  9. data/lib/active_record/associations/belongs_to_association.rb +7 -3
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  11. data/lib/active_record/associations/builder/association.rb +23 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +2 -2
  13. data/lib/active_record/associations/has_many_association.rb +1 -1
  14. data/lib/active_record/associations/join_dependency.rb +2 -2
  15. data/lib/active_record/associations/join_dependency/join_association.rb +8 -7
  16. data/lib/active_record/attribute_methods.rb +13 -7
  17. data/lib/active_record/attribute_methods/serialization.rb +8 -2
  18. data/lib/active_record/attributes.rb +6 -1
  19. data/lib/active_record/callbacks.rb +121 -1
  20. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -3
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  23. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +7 -1
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +24 -18
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +23 -8
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +6 -10
  27. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +1 -2
  28. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
  29. data/lib/active_record/connection_adapters/pool_config.rb +13 -3
  30. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  31. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -3
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  35. data/lib/active_record/connection_handling.rb +11 -3
  36. data/lib/active_record/core.rb +51 -32
  37. data/lib/active_record/database_configurations/url_config.rb +1 -1
  38. data/lib/active_record/enum.rb +58 -31
  39. data/lib/active_record/fixtures.rb +4 -1
  40. data/lib/active_record/gem_version.rb +2 -2
  41. data/lib/active_record/insert_all.rb +2 -0
  42. data/lib/active_record/internal_metadata.rb +2 -4
  43. data/lib/active_record/locking/optimistic.rb +14 -4
  44. data/lib/active_record/log_subscriber.rb +3 -2
  45. data/lib/active_record/migration/compatibility.rb +2 -1
  46. data/lib/active_record/model_schema.rb +29 -0
  47. data/lib/active_record/railtie.rb +2 -2
  48. data/lib/active_record/railties/console_sandbox.rb +2 -4
  49. data/lib/active_record/railties/databases.rake +29 -7
  50. data/lib/active_record/reflection.rb +2 -1
  51. data/lib/active_record/relation/predicate_builder.rb +6 -9
  52. data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -4
  53. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
  54. data/lib/active_record/relation/query_methods.rb +9 -6
  55. data/lib/active_record/relation/spawn_methods.rb +2 -2
  56. data/lib/active_record/schema_migration.rb +2 -4
  57. data/lib/active_record/signed_id.rb +1 -1
  58. data/lib/active_record/table_metadata.rb +10 -3
  59. data/lib/active_record/tasks/database_tasks.rb +1 -0
  60. data/lib/active_record/transactions.rb +4 -2
  61. metadata +12 -12
@@ -773,9 +773,12 @@ module ActiveRecord
773
773
 
774
774
  def find
775
775
  raise FixtureClassNotFound, "No class attached to find." unless model_class
776
- model_class.unscoped do
776
+ object = model_class.unscoped do
777
777
  model_class.find(fixture[model_class.primary_key])
778
778
  end
779
+ # Fixtures can't be eagerly loaded
780
+ object.instance_variable_set(:@strict_loading, false)
781
+ object
779
782
  end
780
783
  end
781
784
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 1
12
- TINY = 0
13
- PRE = "rc1"
12
+ TINY = 2
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -69,6 +69,8 @@ module ActiveRecord
69
69
  attr_reader :scope_attributes
70
70
 
71
71
  def find_unique_index_for(unique_by)
72
+ return unique_by if !connection.supports_insert_conflict_target?
73
+
72
74
  name_or_columns = unique_by || model.primary_key
73
75
  match = Array(name_or_columns).map(&:to_s)
74
76
 
@@ -43,11 +43,9 @@ module ActiveRecord
43
43
  def create_table
44
44
  return unless enabled?
45
45
 
46
- unless table_exists?
47
- key_options = connection.internal_string_options_for_primary_key
48
-
46
+ unless connection.table_exists?(table_name)
49
47
  connection.create_table(table_name, id: false) do |t|
50
- t.string :key, **key_options
48
+ t.string :key, **connection.internal_string_options_for_primary_key
51
49
  t.string :value
52
50
  t.timestamps
53
51
  end
@@ -89,7 +89,9 @@ module ActiveRecord
89
89
 
90
90
  begin
91
91
  locking_column = self.class.locking_column
92
- previous_lock_value = attribute_before_type_cast(locking_column)
92
+ lock_attribute_was = @attributes[locking_column]
93
+ lock_value_for_database = _lock_value_for_database(locking_column)
94
+
93
95
  attribute_names = attribute_names.dup if attribute_names.frozen?
94
96
  attribute_names << locking_column
95
97
 
@@ -98,7 +100,7 @@ module ActiveRecord
98
100
  affected_rows = self.class._update_record(
99
101
  attributes_with_values(attribute_names),
100
102
  @primary_key => id_in_database,
101
- locking_column => @attributes[locking_column].original_value_for_database
103
+ locking_column => lock_value_for_database
102
104
  )
103
105
 
104
106
  if affected_rows != 1
@@ -109,7 +111,7 @@ module ActiveRecord
109
111
 
110
112
  # If something went wrong, revert the locking_column value.
111
113
  rescue Exception
112
- self[locking_column] = previous_lock_value.to_i
114
+ @attributes[locking_column] = lock_attribute_was
113
115
  raise
114
116
  end
115
117
  end
@@ -121,7 +123,7 @@ module ActiveRecord
121
123
 
122
124
  affected_rows = self.class._delete_record(
123
125
  @primary_key => id_in_database,
124
- locking_column => attribute_before_type_cast(locking_column)
126
+ locking_column => _lock_value_for_database(locking_column)
125
127
  )
126
128
 
127
129
  if affected_rows != 1
@@ -131,6 +133,14 @@ module ActiveRecord
131
133
  affected_rows
132
134
  end
133
135
 
136
+ def _lock_value_for_database(locking_column)
137
+ if will_save_change_to_attribute?(locking_column)
138
+ @attributes[locking_column].value_for_database
139
+ else
140
+ @attributes[locking_column].original_value_for_database
141
+ end
142
+ end
143
+
134
144
  module ClassMethods
135
145
  DEFAULT_LOCKING_COLUMN = "lock_version"
136
146
 
@@ -22,9 +22,10 @@ module ActiveRecord
22
22
  def strict_loading_violation(event)
23
23
  debug do
24
24
  owner = event.payload[:owner]
25
- association = event.payload[:association]
25
+ association = event.payload[:reflection].klass
26
+ name = event.payload[:reflection].name
26
27
 
27
- color("Strict loading violation: #{association} lazily loaded on #{owner}.", RED)
28
+ color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
28
29
  end
29
30
  end
30
31
 
@@ -56,7 +56,8 @@ module ActiveRecord
56
56
  end
57
57
 
58
58
  def add_reference(table_name, ref_name, **options)
59
- ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
59
+ ReferenceDefinition.new(ref_name, **options)
60
+ .add_to(connection.update_table_definition(table_name, self))
60
61
  end
61
62
  alias :add_belongs_to :add_reference
62
63
 
@@ -297,6 +297,35 @@ module ActiveRecord
297
297
 
298
298
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
299
299
  # accessors defined, and won't be referenced in SQL queries.
300
+ #
301
+ # A common usage pattern for this method is to ensure all references to an attribute
302
+ # have been removed and deployed, before a migration to drop the column from the database
303
+ # has been deployed and run. Using this two step approach to dropping columns ensures there
304
+ # is no code that raises errors due to having a cached schema in memory at the time the
305
+ # schema migration is run.
306
+ #
307
+ # For example, given a model where you want to drop the "category" attribute, first mark it
308
+ # as ignored:
309
+ #
310
+ # class Project < ActiveRecord::Base
311
+ # # schema:
312
+ # # id :bigint
313
+ # # name :string, limit: 255
314
+ # # category :string, limit: 255
315
+ #
316
+ # self.ignored_columns = [:category]
317
+ # end
318
+ #
319
+ # The schema still contains `category`, but now the model omits it, so any meta-driven code or
320
+ # schema caching will not attempt to use the column:
321
+ #
322
+ # Project.columns_hash["category"] => nil
323
+ #
324
+ # You will get an error if accessing that attribute directly, so ensure all usages of the
325
+ # column are removed (automated tests can help you find any usages).
326
+ #
327
+ # user = Project.create!(name: "First Project")
328
+ # user.category # => raises NoMethodError
300
329
  def ignored_columns=(columns)
301
330
  reload_schema_from_cache
302
331
  @ignored_columns = columns.map(&:to_s).freeze
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  config.active_record.maintain_test_schema = true
32
32
  config.active_record.has_many_inversing = false
33
33
 
34
- config.active_record.queues = ActiveSupport::InheritableOptions.new(destroy: :active_record_destroy)
34
+ config.active_record.queues = ActiveSupport::InheritableOptions.new
35
35
 
36
36
  config.eager_load_namespaces << ActiveRecord
37
37
 
@@ -183,7 +183,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
183
183
  end
184
184
  end
185
185
  rescue ActiveRecordError => error
186
- # Regardless of wether there was already a connection or not, we rescue any database
186
+ # Regardless of whether there was already a connection or not, we rescue any database
187
187
  # error because it is critical that the application can boot even if the database
188
188
  # is unhealthy.
189
189
  warn "Failed to define attribute methods because of #{error.class}: #{error.message}"
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
4
-
5
- at_exit do
6
- ActiveRecord::Base.connection.rollback_transaction
3
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after) do
4
+ begin_transaction(joinable: false)
7
5
  end
@@ -361,17 +361,23 @@ db_namespace = namespace :db do
361
361
 
362
362
  # Skipped when no database
363
363
  ActiveRecord::Tasks::DatabaseTasks.migrate
364
+
364
365
  if ActiveRecord::Base.dump_schema_after_migration
365
366
  ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ActiveRecord::Base.schema_format)
366
367
  end
367
-
368
368
  rescue ActiveRecord::NoDatabaseError
369
- ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.name)
370
- ActiveRecord::Tasks::DatabaseTasks.load_schema(
371
- db_config,
372
- ActiveRecord::Base.schema_format,
373
- nil
374
- )
369
+ config_name = db_config.name
370
+ ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, config_name)
371
+
372
+ if File.exist?(ActiveRecord::Tasks::DatabaseTasks.dump_filename(config_name))
373
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(
374
+ db_config,
375
+ ActiveRecord::Base.schema_format,
376
+ nil
377
+ )
378
+ else
379
+ ActiveRecord::Tasks::DatabaseTasks.migrate
380
+ end
375
381
 
376
382
  seed = true
377
383
  end
@@ -455,6 +461,14 @@ db_namespace = namespace :db do
455
461
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"])
456
462
  end
457
463
 
464
+ task load_if_ruby: ["db:create", :environment] do
465
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
466
+ Using `bin/rails db:schema:load_if_ruby` is deprecated and will be removed in Rails 6.2.
467
+ Configure the format using `config.active_record.schema_format = :ruby` to use `schema.rb` and run `bin/rails db:schema:load` instead.
468
+ MSG
469
+ db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby
470
+ end
471
+
458
472
  namespace :dump do
459
473
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
460
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"
@@ -529,6 +543,14 @@ db_namespace = namespace :db do
529
543
  db_namespace["schema:load"].invoke
530
544
  end
531
545
 
546
+ task load_if_sql: ["db:create", :environment] do
547
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
548
+ Using `bin/rails db:structure:load_if_sql` is deprecated and will be removed in Rails 6.2.
549
+ Configure the format using `config.active_record.schema_format = :sql` to use `structure.sql` and run `bin/rails db:schema:load` instead.
550
+ MSG
551
+ db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :sql
552
+ end
553
+
532
554
  namespace :dump do
533
555
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
534
556
  desc "Dumps the #{name} database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
@@ -162,7 +162,7 @@ module ActiveRecord
162
162
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
163
163
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
164
164
  def class_name
165
- @class_name ||= -(options[:class_name]&.to_s || derive_class_name)
165
+ @class_name ||= -(options[:class_name] || derive_class_name).to_s
166
166
  end
167
167
 
168
168
  # Returns a list of scopes that should be applied for this Reflection
@@ -623,6 +623,7 @@ module ActiveRecord
623
623
  # with the current reflection's klass name.
624
624
  def valid_inverse_reflection?(reflection)
625
625
  reflection &&
626
+ foreign_key == reflection.foreign_key &&
626
627
  klass <= reflection.active_record &&
627
628
  can_find_inverse_of_automatically?(reflection)
628
629
  end
@@ -32,9 +32,9 @@ module ActiveRecord
32
32
  def self.references(attributes)
33
33
  attributes.each_with_object([]) do |(key, value), result|
34
34
  if value.is_a?(Hash)
35
- result << key
35
+ result << Arel.sql(key)
36
36
  elsif key.include?(".")
37
- result << key.split(".").first
37
+ result << Arel.sql(key.split(".").first)
38
38
  end
39
39
  end
40
40
  end
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  end
60
60
 
61
61
  def build(attribute, value, operator = nil)
62
- value = value.id if value.is_a?(Base)
62
+ value = value.id if value.respond_to?(:id)
63
63
  if operator ||= table.type(attribute.name).force_equality?(value) && :eq
64
64
  bind = build_bind_attribute(attribute.name, value)
65
65
  attribute.public_send(operator, bind)
@@ -93,14 +93,11 @@ module ActiveRecord
93
93
  # PriceEstimate.where(estimate_of: treasure)
94
94
  associated_table = table.associated_table(key)
95
95
  if associated_table.polymorphic_association?
96
- case value.is_a?(Array) ? value.first : value
97
- when Base, Relation
98
- value = [value] unless value.is_a?(Array)
99
- klass = PolymorphicArrayValue
100
- end
96
+ value = [value] unless value.is_a?(Array)
97
+ klass = PolymorphicArrayValue
101
98
  elsif associated_table.through_association?
102
99
  next associated_table.predicate_builder.expand_from_hash(
103
- associated_table.join_foreign_key => value
100
+ associated_table.primary_key => value
104
101
  )
105
102
  end
106
103
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [associated_table.join_foreign_key => ids]
12
+ [ associated_table.join_foreign_key => ids ]
13
13
  end
14
14
 
15
15
  private
@@ -31,9 +31,8 @@ module ActiveRecord
31
31
  end
32
32
 
33
33
  def convert_to_id(value)
34
- case value
35
- when Base
36
- value._read_attribute(primary_key)
34
+ if value.respond_to?(primary_key)
35
+ value.public_send(primary_key)
37
36
  else
38
37
  value
39
38
  end
@@ -9,11 +9,13 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
+ return [ associated_table.join_foreign_key => values ] if values.empty?
13
+
12
14
  type_to_ids_mapping.map do |type, ids|
13
- {
14
- associated_table.join_foreign_type => type,
15
- associated_table.join_foreign_key => ids
16
- }
15
+ query = {}
16
+ query[associated_table.join_foreign_type] = type if type
17
+ query[associated_table.join_foreign_key] = ids
18
+ query
17
19
  end
18
20
  end
19
21
 
@@ -23,7 +25,7 @@ module ActiveRecord
23
25
  def type_to_ids_mapping
24
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
25
27
  values.each_with_object(default_hash) do |value, hash|
26
- hash[klass(value).polymorphic_name] << convert_to_id(value)
28
+ hash[klass(value)&.polymorphic_name] << convert_to_id(value)
27
29
  end
28
30
  end
29
31
 
@@ -46,6 +48,8 @@ module ActiveRecord
46
48
  value._read_attribute(primary_key(value))
47
49
  when Relation
48
50
  value.select(primary_key(value))
51
+ else
52
+ value
49
53
  end
50
54
  end
51
55
  end
@@ -1081,12 +1081,15 @@ module ActiveRecord
1081
1081
  when String, Array
1082
1082
  parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1083
1083
  when Hash
1084
- opts = opts.stringify_keys
1084
+ opts = opts.transform_keys do |key|
1085
+ key = key.to_s
1086
+ klass.attribute_aliases[key] || key
1087
+ end
1085
1088
  references = PredicateBuilder.references(opts)
1086
1089
  self.references_values |= references unless references.empty?
1087
1090
 
1088
1091
  parts = predicate_builder.build_from_hash(opts) do |table_name|
1089
- lookup_reflection_from_join_dependencies(table_name)
1092
+ lookup_table_klass_from_join_dependencies(table_name)
1090
1093
  end
1091
1094
  when Arel::Nodes::Node
1092
1095
  parts = [opts]
@@ -1099,9 +1102,9 @@ module ActiveRecord
1099
1102
  alias :build_having_clause :build_where_clause
1100
1103
 
1101
1104
  private
1102
- def lookup_reflection_from_join_dependencies(table_name)
1105
+ def lookup_table_klass_from_join_dependencies(table_name)
1103
1106
  each_join_dependencies do |join|
1104
- return join.reflection if table_name == join.table_name
1107
+ return join.base_klass if table_name == join.table_name
1105
1108
  end
1106
1109
  nil
1107
1110
  end
@@ -1312,7 +1315,7 @@ module ActiveRecord
1312
1315
  elsif field.match?(/\A\w+\.\w+\z/)
1313
1316
  table, column = field.split(".")
1314
1317
  predicate_builder.resolve_arel_attribute(table, column) do
1315
- lookup_reflection_from_join_dependencies(table)
1318
+ lookup_table_klass_from_join_dependencies(table)
1316
1319
  end
1317
1320
  else
1318
1321
  yield field
@@ -1505,7 +1508,7 @@ module ActiveRecord
1505
1508
  v1 = v1.uniq
1506
1509
  v2 = v2.uniq
1507
1510
  end
1508
- v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
1511
+ v1 == v2
1509
1512
  end
1510
1513
  end
1511
1514
  end
@@ -69,8 +69,8 @@ module ActiveRecord
69
69
 
70
70
  private
71
71
  def relation_with(values)
72
- result = Relation.create(klass, values: values)
73
- result.extend(*extending_values) if extending_values.any?
72
+ result = spawn
73
+ result.instance_variable_set(:@values, values)
74
74
  result
75
75
  end
76
76
  end
@@ -23,11 +23,9 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def create_table
26
- unless table_exists?
27
- version_options = connection.internal_string_options_for_primary_key
28
-
26
+ unless connection.table_exists?(table_name)
29
27
  connection.create_table(table_name, id: false) do |t|
30
- t.string :version, **version_options
28
+ t.string :version, **connection.internal_string_options_for_primary_key
31
29
  end
32
30
  end
33
31
  end
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # a certain time period.
21
21
  #
22
22
  # You set the time period that the signed id is valid for during generation, using the instance method
23
- # +signed_id(expires_in: 15.minutes)+. If the time has elapsed before a signed find is attempted,
23
+ # <tt>signed_id(expires_in: 15.minutes)</tt>. If the time has elapsed before a signed find is attempted,
24
24
  # the signed id will no longer be valid, and nil is returned.
25
25
  #
26
26
  # It's possible to further restrict the use of a signed id with a purpose. This helps when you have a