activerecord 6.1.0 → 6.1.3

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +195 -15
  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 +7 -7
  7. data/lib/active_record/associations/association_scope.rb +7 -5
  8. data/lib/active_record/associations/belongs_to_association.rb +7 -3
  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/connection_adapters/abstract/connection_pool.rb +4 -4
  18. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  19. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  20. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  21. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +7 -1
  22. data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -8
  23. data/lib/active_record/connection_adapters/mysql/quoting.rb +17 -2
  24. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
  25. data/lib/active_record/connection_adapters/pool_config.rb +13 -3
  26. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  27. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -8
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  30. data/lib/active_record/connection_handling.rb +20 -12
  31. data/lib/active_record/core.rb +42 -24
  32. data/lib/active_record/database_configurations/url_config.rb +1 -1
  33. data/lib/active_record/enum.rb +48 -28
  34. data/lib/active_record/fixtures.rb +5 -2
  35. data/lib/active_record/gem_version.rb +1 -1
  36. data/lib/active_record/locking/optimistic.rb +14 -4
  37. data/lib/active_record/log_subscriber.rb +3 -2
  38. data/lib/active_record/migration/compatibility.rb +2 -1
  39. data/lib/active_record/migration.rb +1 -1
  40. data/lib/active_record/model_schema.rb +4 -4
  41. data/lib/active_record/railties/console_sandbox.rb +2 -4
  42. data/lib/active_record/railties/databases.rake +13 -7
  43. data/lib/active_record/reflection.rb +1 -1
  44. data/lib/active_record/relation/finder_methods.rb +1 -1
  45. data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -3
  46. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
  47. data/lib/active_record/relation/predicate_builder.rb +5 -6
  48. data/lib/active_record/relation/query_methods.rb +8 -5
  49. data/lib/active_record/relation/where_clause.rb +5 -5
  50. data/lib/active_record/relation.rb +1 -2
  51. data/lib/active_record/signed_id.rb +1 -1
  52. data/lib/active_record/table_metadata.rb +6 -3
  53. data/lib/active_record/tasks/database_tasks.rb +1 -0
  54. data/lib/active_record/transactions.rb +4 -2
  55. metadata +13 -13
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/time_with_zone"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module MySQL
@@ -69,10 +71,23 @@ module ActiveRecord
69
71
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
70
72
 
71
73
  private
74
+ # Override +_type_cast+ we pass to mysql2 Date and Time objects instead
75
+ # of Strings since mysql2 is able to handle those classes more efficiently.
72
76
  def _type_cast(value)
73
77
  case value
74
- when Date, Time then value
75
- else super
78
+ when ActiveSupport::TimeWithZone
79
+ # We need to check explicitly for ActiveSupport::TimeWithZone because
80
+ # we need to transform it to Time objects but we don't want to
81
+ # transform Time objects to themselves.
82
+ if ActiveRecord::Base.default_timezone == :utc
83
+ value.getutc
84
+ else
85
+ value.getlocal
86
+ end
87
+ when Date, Time
88
+ value
89
+ else
90
+ super
76
91
  end
77
92
  end
78
93
  end
@@ -79,7 +79,10 @@ module ActiveRecord
79
79
  " WHERE table_schema = #{scope[:schema]}" \
80
80
  " AND table_name = #{scope[:name]}" \
81
81
  " AND column_name = #{column_name}"
82
- @connection.query_value(sql, "SCHEMA").inspect
82
+ # Calling .inspect leads into issues with the query result
83
+ # which already returns escaped quotes.
84
+ # We remove the escape sequence from the result in order to deal with double escaping issues.
85
+ @connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect
83
86
  end
84
87
  end
85
88
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include Mutex_m
7
7
 
8
- attr_reader :db_config, :connection_specification_name
8
+ attr_reader :db_config, :connection_klass
9
9
  attr_accessor :schema_cache
10
10
 
11
11
  INSTANCES = ObjectSpace::WeakMap.new
@@ -17,14 +17,24 @@ module ActiveRecord
17
17
  end
18
18
  end
19
19
 
20
- def initialize(connection_specification_name, db_config)
20
+ def initialize(connection_klass, db_config)
21
21
  super()
22
- @connection_specification_name = connection_specification_name
22
+ @connection_klass = connection_klass
23
23
  @db_config = db_config
24
24
  @pool = nil
25
25
  INSTANCES[self] = self
26
26
  end
27
27
 
28
+ def connection_specification_name
29
+ if connection_klass.is_a?(String)
30
+ connection_klass
31
+ elsif connection_klass.primary_class?
32
+ "ActiveRecord::Base"
33
+ else
34
+ connection_klass.name
35
+ end
36
+ end
37
+
28
38
  def disconnect!
29
39
  ActiveSupport::ForkTracker.check!
30
40
 
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  ftype = result.ftype i
58
58
  fmod = result.fmod i
59
59
  case type = get_oid_type(ftype, fmod, fname)
60
- when Type::Integer, Type::Float, Type::Decimal, Type::String, Type::DateTime, Type::Boolean
60
+ when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
61
61
  # skip if a column has already been type casted by pg decoders
62
62
  else types[fname] = type
63
63
  end
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
- when /^-?\D*[\d,]+\.\d{2}$/ # (1)
29
+ when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
30
30
  value.gsub!(/[^-\d.]/, "")
31
- when /^-?\D*[\d.]+,\d{2}$/ # (2)
31
+ when /^-?\D*+[\d.]+,\d{2}$/ # (2)
32
32
  value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
33
33
  end
34
34
 
@@ -227,11 +227,7 @@ module ActiveRecord
227
227
  end
228
228
 
229
229
  def next_key
230
- "a#{@counter + 1}"
231
- end
232
-
233
- def []=(sql, key)
234
- super.tap { @counter += 1 }
230
+ "a#{@counter += 1}"
235
231
  end
236
232
 
237
233
  private
@@ -649,9 +645,7 @@ module ActiveRecord
649
645
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
650
646
  end
651
647
 
652
- if without_prepared_statement?(binds)
653
- result = exec_no_cache(sql, name, [])
654
- elsif !prepare
648
+ if !prepare || without_prepared_statement?(binds)
655
649
  result = exec_no_cache(sql, name, binds)
656
650
  else
657
651
  result = exec_cache(sql, name, binds)
@@ -271,7 +271,7 @@ module ActiveRecord
271
271
  def change_column(table_name, column_name, type, **options) #:nodoc:
272
272
  alter_table(table_name) do |definition|
273
273
  definition[column_name].instance_eval do
274
- self.type = type
274
+ self.type = aliased_types(type.to_s, type)
275
275
  self.options.merge!(options)
276
276
  end
277
277
  end
@@ -91,6 +91,7 @@ module ActiveRecord
91
91
  db_config, owner_name = resolve_config_for_connection(database_key)
92
92
  handler = lookup_connection_handler(role.to_sym)
93
93
 
94
+ self.connection_class = true
94
95
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
95
96
  end
96
97
 
@@ -99,6 +100,7 @@ module ActiveRecord
99
100
  db_config, owner_name = resolve_config_for_connection(database_key)
100
101
  handler = lookup_connection_handler(role.to_sym)
101
102
 
103
+ self.connection_class = true
102
104
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
103
105
  end
104
106
  end
@@ -112,7 +114,7 @@ module ActiveRecord
112
114
  #
113
115
  # If only a role is passed, Active Record will look up the connection
114
116
  # based on the requested role. If a non-established role is requested
115
- # an `ActiveRecord::ConnectionNotEstablished` error will be raised:
117
+ # an +ActiveRecord::ConnectionNotEstablished+ error will be raised:
116
118
  #
117
119
  # ActiveRecord::Base.connected_to(role: :writing) do
118
120
  # Dog.create! # creates dog using dog writing connection
@@ -123,7 +125,7 @@ module ActiveRecord
123
125
  # end
124
126
  #
125
127
  # When swapping to a shard, the role must be passed as well. If a non-existent
126
- # shard is passed, an `ActiveRecord::ConnectionNotEstablished` error will be
128
+ # shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be
127
129
  # raised.
128
130
  #
129
131
  # When a shard and role is passed, Active Record will first lookup the role,
@@ -143,6 +145,10 @@ module ActiveRecord
143
145
  if self != Base && !abstract_class
144
146
  raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
145
147
  end
148
+
149
+ if name != connection_specification_name && !primary_class?
150
+ raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
151
+ end
146
152
  end
147
153
 
148
154
  if database && (role || shard)
@@ -172,20 +178,22 @@ module ActiveRecord
172
178
  end
173
179
  end
174
180
 
175
- # Connects a role and/or shard to the provided connection names. Optionally `prevent_writes`
176
- # can be passed to block writes on a connection. `reading` will automatically set
177
- # `prevent_writes` to true.
181
+ # Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+
182
+ # can be passed to block writes on a connection. +reading+ will automatically set
183
+ # +prevent_writes+ to true.
178
184
  #
179
- # `connected_to_many` is an alternative to deeply nested `connected_to` blocks.
185
+ # +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks.
180
186
  #
181
187
  # Usage:
182
188
  #
183
- # ActiveRecord::Base.connected_to(AnimalsRecord, MealsRecord], role: :reading) do
189
+ # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
184
190
  # Dog.first # Read from animals replica
185
191
  # Dinner.first # Read from meals replica
186
192
  # Person.first # Read from primary writer
187
193
  # end
188
- def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
194
+ def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
195
+ classes = classes.flatten
196
+
189
197
  if legacy_connection_handling
190
198
  raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
191
199
  end
@@ -208,7 +216,7 @@ module ActiveRecord
208
216
  # being used. For example, when booting a console in readonly mode.
209
217
  #
210
218
  # It is not recommended to use this method in a request since it
211
- # does not yield to a block like `connected_to`.
219
+ # does not yield to a block like +connected_to+.
212
220
  def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
213
221
  if legacy_connection_handling
214
222
  raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
@@ -222,13 +230,13 @@ module ActiveRecord
222
230
  # Prevent writing to the database regardless of role.
223
231
  #
224
232
  # In some cases you may want to prevent writes to the database
225
- # even if you are on a database that can write. `while_preventing_writes`
233
+ # even if you are on a database that can write. +while_preventing_writes+
226
234
  # will prevent writes to the database for the duration of the block.
227
235
  #
228
236
  # This method does not provide the same protection as a readonly
229
237
  # user and is meant to be a safeguard against accidental writes.
230
238
  #
231
- # See `READ_QUERY` for the queries that are blocked by this
239
+ # See +READ_QUERY+ for the queries that are blocked by this
232
240
  # method.
233
241
  def while_preventing_writes(enabled = true, &block)
234
242
  if legacy_connection_handling
@@ -357,7 +365,7 @@ module ActiveRecord
357
365
  self.connection_specification_name = owner_name
358
366
 
359
367
  db_config = Base.configurations.resolve(config_or_env)
360
- [db_config, owner_name]
368
+ [db_config, self]
361
369
  end
362
370
 
363
371
  def with_handler(handler_key, &blk)
@@ -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
@@ -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
@@ -158,8 +158,6 @@ module ActiveRecord
158
158
  end
159
159
 
160
160
  def enum(definitions)
161
- klass = self
162
-
163
161
  enum_prefix = definitions.delete(:_prefix)
164
162
  enum_suffix = definitions.delete(:_suffix)
165
163
  enum_scopes = definitions.delete(:_scopes)
@@ -187,55 +185,77 @@ module ActiveRecord
187
185
  EnumType.new(attr, enum_values, subtype)
188
186
  end
189
187
 
188
+ value_method_names = []
190
189
  _enum_methods_module.module_eval do
190
+ prefix = if enum_prefix == true
191
+ "#{name}_"
192
+ elsif enum_prefix
193
+ "#{enum_prefix}_"
194
+ end
195
+
196
+ suffix = if enum_suffix == true
197
+ "_#{name}"
198
+ elsif enum_suffix
199
+ "_#{enum_suffix}"
200
+ end
201
+
191
202
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
192
- value_method_names = []
193
203
  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
204
  enum_values[label] = value
209
205
  label = label.to_s
210
206
 
211
- # def active?() status == "active" end
207
+ value_method_name = "#{prefix}#{label}#{suffix}"
208
+ value_method_names << value_method_name
209
+ define_enum_methods(name, value_method_name, value, enum_scopes)
210
+
211
+ method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
212
+ value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
213
+
214
+ if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
215
+ value_method_names << value_method_alias
216
+ define_enum_methods(name, value_method_alias, value, enum_scopes)
217
+ end
218
+ end
219
+ end
220
+ detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
221
+ enum_values.freeze
222
+ end
223
+ end
224
+
225
+ private
226
+ class EnumMethods < Module # :nodoc:
227
+ def initialize(klass)
228
+ @klass = klass
229
+ end
230
+
231
+ private
232
+ attr_reader :klass
233
+
234
+ def define_enum_methods(name, value_method_name, value, enum_scopes)
235
+ # def active?() status_for_database == 0 end
212
236
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
213
- define_method("#{value_method_name}?") { self[attr] == label }
237
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
214
238
 
215
239
  # def active!() update!(status: 0) end
216
240
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
217
- define_method("#{value_method_name}!") { update!(attr => value) }
241
+ define_method("#{value_method_name}!") { update!(name => value) }
218
242
 
219
243
  # scope :active, -> { where(status: 0) }
220
244
  # scope :not_active, -> { where.not(status: 0) }
221
245
  if enum_scopes != false
222
246
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
223
- klass.scope value_method_name, -> { where(attr => value) }
247
+ klass.scope value_method_name, -> { where(name => value) }
224
248
 
225
249
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
226
- klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
250
+ klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
227
251
  end
228
252
  end
229
- klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
230
- end
231
- enum_values.freeze
232
253
  end
233
- end
254
+ private_constant :EnumMethods
234
255
 
235
- private
236
256
  def _enum_methods_module
237
257
  @_enum_methods_module ||= begin
238
- mod = Module.new
258
+ mod = EnumMethods.new(self)
239
259
  include mod
240
260
  mod
241
261
  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 = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -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