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
@@ -751,6 +751,11 @@ module ActiveRecord
751
751
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
752
752
  variables["wait_timeout"] = wait_timeout
753
753
 
754
+ # Set the collation of the connection character set.
755
+ if @config[:collation]
756
+ variables["collation_connection"] = @config[:collation]
757
+ end
758
+
754
759
  defaults = [":default", :default].to_set
755
760
 
756
761
  # Make MySQL reject illegal values rather than truncating or blanking them, see
@@ -770,15 +775,6 @@ module ActiveRecord
770
775
  end
771
776
  sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
772
777
 
773
- # NAMES does not have an equals sign, see
774
- # https://dev.mysql.com/doc/refman/en/set-names.html
775
- # (trailing comma because variable_assignments will always have content)
776
- if @config[:encoding]
777
- encoding = +"NAMES #{@config[:encoding]}"
778
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
779
- encoding << ", "
780
- end
781
-
782
778
  # Gather up all of the SET variables...
783
779
  variable_assignments = variables.map do |k, v|
784
780
  if defaults.include?(v)
@@ -790,7 +786,7 @@ module ActiveRecord
790
786
  end.compact.join(", ")
791
787
 
792
788
  # ...and send them all in one query
793
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
789
+ execute("SET #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
794
790
  end
795
791
 
796
792
  def column_definitions(table_name) # :nodoc:
@@ -45,10 +45,9 @@ module ActiveRecord
45
45
  end
46
46
 
47
47
  def add_table_options!(create_sql, o)
48
- create_sql = super
49
48
  create_sql << " DEFAULT CHARSET=#{o.charset}" if o.charset
50
49
  create_sql << " COLLATE=#{o.collation}" if o.collation
51
- add_sql_comment!(create_sql, o.comment)
50
+ add_sql_comment!(super, o.comment)
52
51
  end
53
52
 
54
53
  def add_column_options!(sql, options)
@@ -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
@@ -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
@@ -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
 
@@ -649,9 +649,7 @@ module ActiveRecord
649
649
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
650
650
  end
651
651
 
652
- if without_prepared_statement?(binds)
653
- result = exec_no_cache(sql, name, [])
654
- elsif !prepare
652
+ if !prepare || without_prepared_statement?(binds)
655
653
  result = exec_no_cache(sql, name, binds)
656
654
  else
657
655
  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
@@ -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)
@@ -180,12 +186,14 @@ module ActiveRecord
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
@@ -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)
@@ -166,10 +166,18 @@ module ActiveRecord
166
166
  end
167
167
 
168
168
  def self.connection_handlers
169
+ unless legacy_connection_handling
170
+ raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
171
+ end
172
+
169
173
  @@connection_handlers ||= {}
170
174
  end
171
175
 
172
176
  def self.connection_handlers=(handlers)
177
+ unless legacy_connection_handling
178
+ raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers."
179
+ end
180
+
173
181
  @@connection_handlers = handlers
174
182
  end
175
183
 
@@ -188,7 +196,7 @@ module ActiveRecord
188
196
  else
189
197
  connected_to_stack.reverse_each do |hash|
190
198
  return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
191
- return hash[:role] if hash[:role] && hash[:klasses].include?(abstract_base_class)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
192
200
  end
193
201
 
194
202
  default_role
@@ -207,7 +215,7 @@ module ActiveRecord
207
215
  def self.current_shard
208
216
  connected_to_stack.reverse_each do |hash|
209
217
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
210
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(abstract_base_class)
218
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
211
219
  end
212
220
 
213
221
  default_shard
@@ -229,7 +237,7 @@ module ActiveRecord
229
237
  else
230
238
  connected_to_stack.reverse_each do |hash|
231
239
  return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
232
- 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)
233
241
  end
234
242
 
235
243
  false
@@ -246,11 +254,23 @@ module ActiveRecord
246
254
  end
247
255
  end
248
256
 
249
- 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:
250
270
  klass = self
251
271
 
252
272
  until klass == Base
253
- break if klass.abstract_class?
273
+ break if klass.connection_class?
254
274
  klass = klass.superclass
255
275
  end
256
276
 
@@ -269,14 +289,14 @@ module ActiveRecord
269
289
  self.default_role = writing_role
270
290
  self.default_shard = :default
271
291
 
272
- def self.strict_loading_violation!(owner:, association:) # :nodoc:
292
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
273
293
  case action_on_strict_loading_violation
274
294
  when :raise
275
- 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."
276
296
  raise ActiveRecord::StrictLoadingViolationError.new(message)
277
297
  when :log
278
298
  name = "strict_loading_violation.active_record"
279
- ActiveSupport::Notifications.instrument(name, owner: owner, association: association)
299
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
280
300
  end
281
301
  end
282
302
  end
@@ -324,31 +344,37 @@ module ActiveRecord
324
344
  hash = args.first
325
345
  return super unless Hash === hash
326
346
 
327
- values = hash.values.map! { |value| value.is_a?(Base) ? value.id : value }
328
- return super if values.any? { |v| StatementCache.unsupported_value?(v) }
329
-
330
- keys = hash.keys.map! do |key|
331
- attribute_aliases[name = key.to_s] || begin
332
- reflection = _reflect_on_association(name)
333
- if reflection&.belongs_to? && !reflection.polymorphic?
334
- reflection.join_foreign_key
335
- elsif reflect_on_aggregation(name)
336
- return super
337
- else
338
- name
339
- 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)
340
361
  end
341
- end
342
362
 
343
- 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
344
369
 
370
+ keys = hash.keys
345
371
  statement = cached_find_by_statement(keys) { |params|
346
372
  wheres = keys.index_with { params.bind }
347
373
  where(wheres).limit(1)
348
374
  }
349
375
 
350
376
  begin
351
- statement.execute(values, connection).first
377
+ statement.execute(hash.values, connection).first
352
378
  rescue TypeError
353
379
  raise ActiveRecord::StatementInvalid
354
380
  end
@@ -664,14 +690,7 @@ module ActiveRecord
664
690
  inspection = if defined?(@attributes) && @attributes
665
691
  self.class.attribute_names.collect do |name|
666
692
  if _has_attribute?(name)
667
- attr = _read_attribute(name)
668
- value = if attr.nil?
669
- attr.inspect
670
- else
671
- attr = format_for_inspect(attr)
672
- inspection_filter.filter_param(name, attr)
673
- end
674
- "#{name}: #{value}"
693
+ "#{name}: #{attribute_for_inspect(name)}"
675
694
  end
676
695
  end.compact.join(", ")
677
696
  else
@@ -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,54 +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
203
  pairs.each do |label, value|
193
- if enum_prefix == true
194
- prefix = "#{name}_"
195
- elsif enum_prefix
196
- prefix = "#{enum_prefix}_"
197
- end
198
- if enum_suffix == true
199
- suffix = "_#{name}"
200
- elsif enum_suffix
201
- suffix = "_#{enum_suffix}"
202
- end
203
-
204
- method_friendly_label = label.to_s.gsub(/\W+/, "_")
205
- value_method_name = "#{prefix}#{method_friendly_label}#{suffix}"
206
204
  enum_values[label] = value
207
205
  label = label.to_s
208
206
 
209
- # 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
210
236
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
211
- define_method("#{value_method_name}?") { self[attr] == label }
237
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
212
238
 
213
239
  # def active!() update!(status: 0) end
214
240
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
215
- define_method("#{value_method_name}!") { update!(attr => value) }
241
+ define_method("#{value_method_name}!") { update!(name => value) }
216
242
 
217
243
  # scope :active, -> { where(status: 0) }
218
244
  # scope :not_active, -> { where.not(status: 0) }
219
245
  if enum_scopes != false
220
- klass.send(:detect_negative_condition!, value_method_name)
221
-
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
- end
230
- enum_values.freeze
231
253
  end
232
- end
254
+ private_constant :EnumMethods
233
255
 
234
- private
235
256
  def _enum_methods_module
236
257
  @_enum_methods_module ||= begin
237
- mod = Module.new
258
+ mod = EnumMethods.new(self)
238
259
  include mod
239
260
  mod
240
261
  end
@@ -281,10 +302,16 @@ module ActiveRecord
281
302
  }
282
303
  end
283
304
 
284
- def detect_negative_condition!(method_name)
285
- if method_name.start_with?("not_") && logger
286
- logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
287
- " This will cause a conflict with auto generated negative scopes."
305
+ def detect_negative_enum_conditions!(method_names)
306
+ return unless logger
307
+
308
+ method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
309
+ inverted_form = potential_not.sub("not_", "")
310
+ if method_names.include?(inverted_form)
311
+ logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
312
+ " This has caused a conflict with auto generated negative scopes." \
313
+ " Avoid using enum elements starting with 'not' where the positive form is also an element."
314
+ end
288
315
  end
289
316
  end
290
317
  end