activerecord 6.1.0 → 6.1.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +305 -17
  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/association.rb +13 -7
  7. data/lib/active_record/associations/association_scope.rb +7 -5
  8. data/lib/active_record/associations/belongs_to_association.rb +8 -5
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +2 -2
  12. data/lib/active_record/associations/has_many_association.rb +1 -1
  13. data/lib/active_record/associations/join_dependency/join_association.rb +8 -7
  14. data/lib/active_record/associations/join_dependency.rb +1 -1
  15. data/lib/active_record/associations.rb +6 -2
  16. data/lib/active_record/attributes.rb +1 -1
  17. data/lib/active_record/coders/yaml_column.rb +11 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +4 -4
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_creation.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 +14 -3
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -11
  26. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  27. data/lib/active_record/connection_adapters/mysql/quoting.rb +17 -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/pool_manager.rb +5 -1
  31. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -8
  34. data/lib/active_record/connection_adapters/schema_cache.rb +9 -1
  35. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  37. data/lib/active_record/connection_adapters.rb +2 -0
  38. data/lib/active_record/connection_handling.rb +20 -12
  39. data/lib/active_record/core.rb +58 -29
  40. data/lib/active_record/database_configurations/url_config.rb +1 -1
  41. data/lib/active_record/enum.rb +52 -34
  42. data/lib/active_record/fixtures.rb +5 -2
  43. data/lib/active_record/gem_version.rb +1 -1
  44. data/lib/active_record/insert_all.rb +5 -1
  45. data/lib/active_record/locking/optimistic.rb +14 -4
  46. data/lib/active_record/log_subscriber.rb +3 -2
  47. data/lib/active_record/migration/compatibility.rb +2 -1
  48. data/lib/active_record/migration.rb +1 -1
  49. data/lib/active_record/model_schema.rb +4 -4
  50. data/lib/active_record/railties/console_sandbox.rb +2 -4
  51. data/lib/active_record/railties/databases.rake +16 -9
  52. data/lib/active_record/reflection.rb +1 -1
  53. data/lib/active_record/relation/calculations.rb +6 -2
  54. data/lib/active_record/relation/finder_methods.rb +1 -1
  55. data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -3
  56. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
  57. data/lib/active_record/relation/predicate_builder.rb +5 -6
  58. data/lib/active_record/relation/query_methods.rb +9 -6
  59. data/lib/active_record/relation/where_clause.rb +17 -14
  60. data/lib/active_record/relation.rb +10 -17
  61. data/lib/active_record/scoping/default.rb +1 -3
  62. data/lib/active_record/signed_id.rb +1 -1
  63. data/lib/active_record/statement_cache.rb +2 -2
  64. data/lib/active_record/table_metadata.rb +6 -3
  65. data/lib/active_record/tasks/database_tasks.rb +1 -0
  66. data/lib/active_record/test_fixtures.rb +42 -1
  67. data/lib/active_record/transactions.rb +4 -2
  68. data/lib/active_record/validations/numericality.rb +1 -1
  69. data/lib/arel/collectors/bind.rb +2 -2
  70. data/lib/arel/collectors/composite.rb +3 -3
  71. data/lib/arel/collectors/sql_string.rb +1 -1
  72. data/lib/arel/collectors/substitute_binds.rb +1 -1
  73. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  74. data/lib/arel/predications.rb +2 -2
  75. data/lib/arel/visitors/to_sql.rb +1 -1
  76. metadata +10 -10
@@ -196,7 +196,7 @@ module ActiveRecord
196
196
  else
197
197
  connected_to_stack.reverse_each do |hash|
198
198
  return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
199
- return hash[:role] if hash[:role] && hash[:klasses].include?(abstract_base_class)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
200
200
  end
201
201
 
202
202
  default_role
@@ -215,7 +215,7 @@ module ActiveRecord
215
215
  def self.current_shard
216
216
  connected_to_stack.reverse_each do |hash|
217
217
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
218
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(abstract_base_class)
218
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
219
219
  end
220
220
 
221
221
  default_shard
@@ -237,7 +237,7 @@ module ActiveRecord
237
237
  else
238
238
  connected_to_stack.reverse_each do |hash|
239
239
  return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
240
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(abstract_base_class)
240
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
241
241
  end
242
242
 
243
243
  false
@@ -254,11 +254,23 @@ module ActiveRecord
254
254
  end
255
255
  end
256
256
 
257
- def self.abstract_base_class # :nodoc:
257
+ def self.connection_class=(b) # :nodoc:
258
+ @connection_class = b
259
+ end
260
+
261
+ def self.connection_class # :nodoc
262
+ @connection_class ||= false
263
+ end
264
+
265
+ def self.connection_class? # :nodoc:
266
+ self.connection_class
267
+ end
268
+
269
+ def self.connection_classes # :nodoc:
258
270
  klass = self
259
271
 
260
272
  until klass == Base
261
- break if klass.abstract_class?
273
+ break if klass.connection_class?
262
274
  klass = klass.superclass
263
275
  end
264
276
 
@@ -277,14 +289,14 @@ module ActiveRecord
277
289
  self.default_role = writing_role
278
290
  self.default_shard = :default
279
291
 
280
- def self.strict_loading_violation!(owner:, association:) # :nodoc:
292
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
281
293
  case action_on_strict_loading_violation
282
294
  when :raise
283
- message = "`#{association}` called on `#{owner}` is marked for strict_loading and cannot be lazily loaded."
295
+ message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
284
296
  raise ActiveRecord::StrictLoadingViolationError.new(message)
285
297
  when :log
286
298
  name = "strict_loading_violation.active_record"
287
- ActiveSupport::Notifications.instrument(name, owner: owner, association: association)
299
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
288
300
  end
289
301
  end
290
302
  end
@@ -332,31 +344,37 @@ module ActiveRecord
332
344
  hash = args.first
333
345
  return super unless Hash === hash
334
346
 
335
- values = hash.values.map! { |value| value.respond_to?(:id) ? value.id : value }
336
- return super if values.any? { |v| StatementCache.unsupported_value?(v) }
337
-
338
- keys = hash.keys.map! do |key|
339
- attribute_aliases[name = key.to_s] || begin
340
- reflection = _reflect_on_association(name)
341
- if reflection&.belongs_to? && !reflection.polymorphic?
342
- reflection.join_foreign_key
343
- elsif reflect_on_aggregation(name)
344
- return super
345
- else
346
- name
347
- end
347
+ hash = hash.each_with_object({}) do |(key, value), h|
348
+ key = key.to_s
349
+ key = attribute_aliases[key] || key
350
+
351
+ return super if reflect_on_aggregation(key)
352
+
353
+ reflection = _reflect_on_association(key)
354
+
355
+ if !reflection
356
+ value = value.id if value.respond_to?(:id)
357
+ elsif reflection.belongs_to? && !reflection.polymorphic?
358
+ key = reflection.join_foreign_key
359
+ pkey = reflection.join_primary_key
360
+ value = value.public_send(pkey) if value.respond_to?(pkey)
348
361
  end
349
- end
350
362
 
351
- return super unless keys.all? { |k| columns_hash.key?(k) }
363
+ if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
364
+ return super
365
+ end
366
+
367
+ h[key] = value
368
+ end
352
369
 
370
+ keys = hash.keys
353
371
  statement = cached_find_by_statement(keys) { |params|
354
372
  wheres = keys.index_with { params.bind }
355
373
  where(wheres).limit(1)
356
374
  }
357
375
 
358
376
  begin
359
- statement.execute(values, connection).first
377
+ statement.execute(hash.values, connection).first
360
378
  rescue TypeError
361
379
  raise ActiveRecord::StatementInvalid
362
380
  end
@@ -390,7 +408,21 @@ module ActiveRecord
390
408
  end
391
409
 
392
410
  # Specifies columns which shouldn't be exposed while calling +#inspect+.
393
- attr_writer :filter_attributes
411
+ def filter_attributes=(filter_attributes)
412
+ @inspection_filter = nil
413
+ @filter_attributes = filter_attributes
414
+ end
415
+
416
+ def inspection_filter # :nodoc:
417
+ if defined?(@filter_attributes)
418
+ @inspection_filter ||= begin
419
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
420
+ ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
421
+ end
422
+ else
423
+ superclass.inspection_filter
424
+ end
425
+ end
394
426
 
395
427
  # Returns a string like 'Post(id:integer, title:string, body:text)'
396
428
  def inspect # :nodoc:
@@ -758,10 +790,7 @@ module ActiveRecord
758
790
  private_constant :InspectionMask
759
791
 
760
792
  def inspection_filter
761
- @inspection_filter ||= begin
762
- mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
763
- ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
764
- end
793
+ self.class.inspection_filter
765
794
  end
766
795
  end
767
796
  end
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  # Return a Hash that can be merged into the main config that represents
43
43
  # the passed in url
44
44
  def build_url_hash
45
- if url.nil? || url.start_with?("jdbc:")
45
+ if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) }
46
46
  { url: url }
47
47
  else
48
48
  ConnectionUrlResolver.new(url).to_hash
@@ -139,27 +139,23 @@ module ActiveRecord
139
139
  mapping.key(subtype.deserialize(value))
140
140
  end
141
141
 
142
- def serializable?(value)
143
- (value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
144
- end
145
-
146
142
  def serialize(value)
147
143
  mapping.fetch(value, value)
148
144
  end
149
145
 
150
146
  def assert_valid_value(value)
151
- unless serializable?(value)
147
+ unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
152
148
  raise ArgumentError, "'#{value}' is not a valid #{name}"
153
149
  end
154
150
  end
155
151
 
152
+ attr_reader :subtype
153
+
156
154
  private
157
- attr_reader :name, :mapping, :subtype
155
+ attr_reader :name, :mapping
158
156
  end
159
157
 
160
158
  def enum(definitions)
161
- klass = self
162
-
163
159
  enum_prefix = definitions.delete(:_prefix)
164
160
  enum_suffix = definitions.delete(:_suffix)
165
161
  enum_scopes = definitions.delete(:_scopes)
@@ -187,55 +183,77 @@ module ActiveRecord
187
183
  EnumType.new(attr, enum_values, subtype)
188
184
  end
189
185
 
186
+ value_method_names = []
190
187
  _enum_methods_module.module_eval do
188
+ prefix = if enum_prefix == true
189
+ "#{name}_"
190
+ elsif enum_prefix
191
+ "#{enum_prefix}_"
192
+ end
193
+
194
+ suffix = if enum_suffix == true
195
+ "_#{name}"
196
+ elsif enum_suffix
197
+ "_#{enum_suffix}"
198
+ end
199
+
191
200
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
192
- value_method_names = []
193
201
  pairs.each do |label, value|
194
- if enum_prefix == true
195
- prefix = "#{name}_"
196
- elsif enum_prefix
197
- prefix = "#{enum_prefix}_"
198
- end
199
- if enum_suffix == true
200
- suffix = "_#{name}"
201
- elsif enum_suffix
202
- suffix = "_#{enum_suffix}"
203
- end
204
-
205
- method_friendly_label = label.to_s.gsub(/\W+/, "_")
206
- value_method_name = "#{prefix}#{method_friendly_label}#{suffix}"
207
- value_method_names << value_method_name
208
202
  enum_values[label] = value
209
203
  label = label.to_s
210
204
 
211
- # def active?() status == "active" end
205
+ value_method_name = "#{prefix}#{label}#{suffix}"
206
+ value_method_names << value_method_name
207
+ define_enum_methods(name, value_method_name, value, enum_scopes)
208
+
209
+ method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
210
+ value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
211
+
212
+ if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
213
+ value_method_names << value_method_alias
214
+ define_enum_methods(name, value_method_alias, value, enum_scopes)
215
+ end
216
+ end
217
+ end
218
+ detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
219
+ enum_values.freeze
220
+ end
221
+ end
222
+
223
+ private
224
+ class EnumMethods < Module # :nodoc:
225
+ def initialize(klass)
226
+ @klass = klass
227
+ end
228
+
229
+ private
230
+ attr_reader :klass
231
+
232
+ def define_enum_methods(name, value_method_name, value, enum_scopes)
233
+ # def active?() status_for_database == 0 end
212
234
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
213
- define_method("#{value_method_name}?") { self[attr] == label }
235
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
214
236
 
215
237
  # def active!() update!(status: 0) end
216
238
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
217
- define_method("#{value_method_name}!") { update!(attr => value) }
239
+ define_method("#{value_method_name}!") { update!(name => value) }
218
240
 
219
241
  # scope :active, -> { where(status: 0) }
220
242
  # scope :not_active, -> { where.not(status: 0) }
221
243
  if enum_scopes != false
222
244
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
223
- klass.scope value_method_name, -> { where(attr => value) }
245
+ klass.scope value_method_name, -> { where(name => value) }
224
246
 
225
247
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
226
- klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
248
+ klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
227
249
  end
228
250
  end
229
- klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
230
- end
231
- enum_values.freeze
232
251
  end
233
- end
252
+ private_constant :EnumMethods
234
253
 
235
- private
236
254
  def _enum_methods_module
237
255
  @_enum_methods_module ||= begin
238
- mod = Module.new
256
+ mod = EnumMethods.new(self)
239
257
  include mod
240
258
  mod
241
259
  end
@@ -181,7 +181,7 @@ module ActiveRecord
181
181
  # end
182
182
  # end
183
183
  #
184
- # If you preload your test database with all fixture data (probably by running `bin/rails db:fixtures:load`)
184
+ # If you preload your test database with all fixture data (probably by running <tt>bin/rails db:fixtures:load</tt>)
185
185
  # and use transactional tests, then you may omit all fixtures declarations in your test cases since
186
186
  # all the data's already there and every case rolls back its changes.
187
187
  #
@@ -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,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 1
12
- TINY = 0
12
+ TINY = 4
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -69,7 +69,11 @@ 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?
72
+ if !connection.supports_insert_conflict_target?
73
+ return if unique_by.nil?
74
+
75
+ raise ArgumentError, "#{connection.class} does not support :unique_by"
76
+ end
73
77
 
74
78
  name_or_columns = unique_by || model.primary_key
75
79
  match = Array(name_or_columns).map(&:to_s)
@@ -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
 
@@ -320,7 +320,7 @@ module ActiveRecord
320
320
  # +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
321
321
  # as +default_or_changes+ will make this change reversible in the migration.
322
322
  # * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
323
- # Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
323
+ # Sets or removes a <tt>NOT NULL</tt> constraint on +column_name+. The +null+ flag
324
324
  # indicates whether the value can be +NULL+. See
325
325
  # ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
326
326
  # details.
@@ -122,9 +122,9 @@ module ActiveRecord
122
122
  # :singleton-method: immutable_strings_by_default=
123
123
  # :call-seq: immutable_strings_by_default=(bool)
124
124
  #
125
- # Determines whether columns should infer their type as `:string` or
126
- # `:immutable_string`. This setting does not affect the behavior of
127
- # `attribute :foo, :string`. Defaults to false.
125
+ # Determines whether columns should infer their type as +:string+ or
126
+ # +:immutable_string+. This setting does not affect the behavior of
127
+ # <tt>attribute :foo, :string</tt>. Defaults to false.
128
128
 
129
129
  included do
130
130
  mattr_accessor :primary_key_prefix_type, instance_writer: false
@@ -316,7 +316,7 @@ module ActiveRecord
316
316
  # self.ignored_columns = [:category]
317
317
  # end
318
318
  #
319
- # The schema still contains `category`, but now the model omits it, so any meta-driven code or
319
+ # The schema still contains "category", but now the model omits it, so any meta-driven code or
320
320
  # schema caching will not attempt to use the column:
321
321
  #
322
322
  # Project.columns_hash["category"] => nil
@@ -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
@@ -407,8 +413,9 @@ db_namespace = namespace :db do
407
413
  fixture_files = if ENV["FIXTURES"]
408
414
  ENV["FIXTURES"].split(",")
409
415
  else
410
- # The use of String#[] here is to support namespaced fixtures.
411
- Dir["#{fixtures_dir}/**/*.yml"].map { |f| f[(fixtures_dir.size + 1)..-5] }
416
+ files = Dir[File.join(fixtures_dir, "**/*.{yml}")]
417
+ files.reject! { |f| f.start_with?(File.join(fixtures_dir, "files")) }
418
+ files.map! { |f| f[fixtures_dir.to_s.size..-5].delete_prefix("/") }
412
419
  end
413
420
 
414
421
  ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files)
@@ -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
@@ -310,6 +310,7 @@ module ActiveRecord
310
310
  type_cast_calculated_value(result.cast_values.first, operation) do |value|
311
311
  type = column.try(:type_caster) ||
312
312
  lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
313
+ type = type.subtype if Enum::EnumType === type
313
314
  type.deserialize(value)
314
315
  end
315
316
  end
@@ -387,8 +388,11 @@ module ActiveRecord
387
388
  key = key_records[key] if associated
388
389
 
389
390
  result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
390
- type ||= column.try(:type_caster) ||
391
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
391
+ unless type
392
+ type = column.try(:type_caster) ||
393
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
394
+ type = type.subtype if Enum::EnumType === type
395
+ end
392
396
  type.deserialize(value)
393
397
  end
394
398
  end
@@ -326,7 +326,7 @@ module ActiveRecord
326
326
  # compared to the records in memory. If the relation is unloaded, an
327
327
  # efficient existence query is performed, as in #exists?.
328
328
  def include?(record)
329
- if loaded? || offset_value || limit_value
329
+ if loaded? || offset_value || limit_value || having_clause.any?
330
330
  records.include?(record)
331
331
  else
332
332
  record.is_a?(klass) && exists?(record.id)
@@ -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,8 +31,8 @@ module ActiveRecord
31
31
  end
32
32
 
33
33
  def convert_to_id(value)
34
- if value.respond_to?(:id)
35
- value.id
34
+ if value.respond_to?(primary_key)
35
+ value.public_send(primary_key)
36
36
  else
37
37
  value
38
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
@@ -93,11 +93,8 @@ 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
100
  associated_table.primary_key => value
@@ -106,7 +103,9 @@ module ActiveRecord
106
103
 
107
104
  klass ||= AssociationQueryValue
108
105
  queries = klass.new(associated_table, value).queries.map! do |query|
109
- expand_from_hash(query)
106
+ # If the query produced is identical to attributes don't go any deeper.
107
+ # Prevents stack level too deep errors when association and foreign_key are identical.
108
+ query == attributes ? self[key, value] : expand_from_hash(query)
110
109
  end
111
110
 
112
111
  grouping_queries(queries)