Skip to content

Commit afc6a59

Browse files
committed
Deprecate #not() (without arguments)
Also, clean up and standardize the deprecation warnings a bit
1 parent 7150a40 commit afc6a59

5 files changed

Lines changed: 83 additions & 22 deletions

File tree

lib/mongoid/criteria/queryable/selectable.rb

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -491,23 +491,28 @@ def negating?
491491
!!negating
492492
end
493493

494-
# Negate the arguments, or the next selection if no arguments are given.
495-
#
496-
# @example Negate the next selection.
497-
# selectable.not.in(field: [ 1, 2 ])
494+
# Negate the arguments.
498495
#
499496
# @example Add the $not criterion.
500-
# selectable.not(name: /Bob/)
497+
# Person.not(name: /Bob/)
501498
#
502-
# @example Execute a $not in a where query.
503-
# selectable.where(:field.not => /Bob/)
499+
# @example Negate a Criteria instance
500+
# Person.not(Person.where(...))
504501
#
505502
# @param [ [ Hash | Criteria ]... ] *criteria The key/value pair
506503
# matches or Criteria objects to negate.
507504
#
508505
# @return [ Selectable ] The new selectable.
509506
def not(*criteria)
510507
if criteria.empty?
508+
# @deprecated
509+
Mongoid.deprecation_warning(
510+
:not_sans_arguments,
511+
'Calling `#not` without arguments is deprecated, and will be ' \
512+
'removed in the next major version. Instead, pass the Hash or ' \
513+
'Criteria instance to negate.',
514+
caller_locations
515+
)
511516
dup.tap { |query| query.negating = !query.negating }
512517
else
513518
criteria.compact.inject(clone) do |c, new_s|

lib/mongoid/deprecable.rb

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ module Mongoid
77
#
88
# @api private
99
module Deprecable
10+
# A Mongoid::Deprecation instance to use for reporting deprecations
11+
def deprecator
12+
@deprecator ||= Mongoid::Deprecation.new
13+
end
14+
15+
# Resets all deprecation warnings. For use in tests.
16+
def reset_deprecation_warnings!
17+
DEPRECATION_WARNING_MUTEX.synchronize { @deprecation_warnings = {} }
18+
end
19+
20+
# Emits a warning using the current deprecator. If the given warning (as
21+
# identified by `id`) has already been issued previously, this is a no-op.
22+
#
23+
# @param [ Symbol ] id The unique identifier for this warning.
24+
# @param [ String ] warning The warning message to emit.
25+
# @param [ Array<Thread::Backtrace::Location> | nil ] callstack The backtrace at the call site.
26+
def deprecation_warning(id, warning, callstack = nil)
27+
site = callstack&.first
28+
deprecation_warning_guard(id, site ? "#{site.path}:#{site.lineno}" : nil) do
29+
deprecator.warn(warning, callstack)
30+
end
31+
end
32+
1033
# Declares method(s) as deprecated.
1134
#
1235
# @example Deprecate a method.
@@ -25,8 +48,28 @@ module Deprecable
2548
# @param [ [ Symbol | Hash<Symbol, [ Symbol | String ]> ]... ] *method_descriptors
2649
# The methods to deprecate, with optional replacement instructions.
2750
def deprecate(target_module, *method_descriptors)
28-
@_deprecator ||= Mongoid::Deprecation.new
29-
@_deprecator.deprecate_methods(target_module, *method_descriptors)
51+
deprecator.deprecate_methods(target_module, *method_descriptors)
52+
end
53+
54+
private
55+
56+
# The Mutex instance used to guard the deprecation warning flags.
57+
DEPRECATION_WARNING_MUTEX = Mutex.new
58+
59+
# Wraps access to the warnings Hash in a synchronize block. If the given
60+
# id+callsite has not been warned already, the method will yield to a block and then
61+
# flag the id. Otherwise, it returns immediately.
62+
def deprecation_warning_guard(id, callsite)
63+
DEPRECATION_WARNING_MUTEX.synchronize do
64+
@deprecation_warnings ||= {}
65+
66+
key = "#{id}:#{callsite}"
67+
return if @deprecation_warnings.key?(key)
68+
69+
yield
70+
71+
@deprecation_warnings[key] = true
72+
end
3073
end
3174
end
3275
end

lib/mongoid/timestamps/timeless.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ module Timestamps
77
module Timeless
88
extend ActiveSupport::Concern
99

10-
# Deprecator for the block-less form of the timeless API. Its removal
11-
# horizon is computed automatically as (current major + 1).0.
12-
DEPRECATION = Mongoid::Deprecation.new
13-
1410
# Clears out the timeless option.
1511
#
1612
# @example Clear the timeless option.
@@ -132,9 +128,17 @@ module ClassMethods
132128
def timeless(&block)
133129
return Timeless.with_timeless(&block) if block
134130

135-
DEPRECATION.warn(
131+
locations = caller_locations
132+
133+
# if this is called from the #timeless instance method, look up one
134+
# level higher for the call site
135+
locations.shift if locations[0].path == __FILE__
136+
137+
Mongoid.deprecation_warning(
138+
:timeless_sans_block,
136139
'Calling #timeless without a block is deprecated; pass a block ' \
137-
'instead, e.g. `record.timeless { record.save }`.'
140+
'instead, e.g. `record.timeless { record.save }`.',
141+
locations
138142
)
139143
counter = 0
140144
counter += 1 if self < Mongoid::Timestamps::Created

spec/mongoid/criteria/queryable/selectable_logical_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,14 @@
15891589
end
15901590

15911591
describe '#not' do
1592+
# deprecated functionality
15921593
context 'when provided no criterion' do
1594+
around do |example|
1595+
Mongoid.deprecator.silence do
1596+
example.run
1597+
end
1598+
end
1599+
15931600
let(:selection) do
15941601
query.not
15951602
end

spec/mongoid/timestamps/timeless_spec.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Chicken
1212
before_save :lay_timeless_egg
1313

1414
def lay_timeless_egg
15-
Egg.timeless.create!
15+
Egg.timeless { Egg.create! }
1616
end
1717
end
1818

@@ -30,7 +30,7 @@ class Egg
3030
context 'when timeless is used on one instance and then not used on another instance' do
3131
let!(:first_instance) do
3232
egg = Egg.create!
33-
egg.timeless.save!
33+
egg.timeless { egg.save! }
3434
egg
3535
end
3636

@@ -60,7 +60,7 @@ class Egg
6060

6161
context 'when the root executes timeless' do
6262
let!(:chicken) do
63-
Chicken.timeless.create!
63+
Chicken.timeless { Chicken.create! }
6464
end
6565

6666
it 'creates the parent with a timestamp' do
@@ -80,7 +80,7 @@ class Egg
8080
end
8181

8282
before do
83-
document.timeless.save!
83+
document.timeless { document.save! }
8484
end
8585

8686
it 'does not set the created timestamp' do
@@ -108,7 +108,7 @@ class Egg
108108

109109
context 'when used on the class' do
110110
let!(:document) do
111-
Dokument.timeless.create!
111+
Dokument.timeless { Dokument.create! }
112112
end
113113

114114
it 'does not set the created timestamp' do
@@ -301,15 +301,17 @@ class TimelessFoo
301301
end
302302

303303
describe 'deprecation of the block-less form' do
304+
before { Mongoid.reset_deprecation_warnings! }
305+
304306
let(:document) { Dokument.new }
305307

306308
it 'warns when called on an instance without a block' do
307-
expect(Mongoid.logger).to receive(:warn).with(/timeless/).and_call_original
309+
expect(Mongoid.logger).to receive(:warn).with(/timeless/)
308310
document.timeless.save!
309311
end
310312

311313
it 'warns when called on the class without a block' do
312-
expect(Mongoid.logger).to receive(:warn).with(/timeless/).and_call_original
314+
expect(Mongoid.logger).to receive(:warn).with(/timeless/)
313315
Dokument.timeless.create!
314316
end
315317

0 commit comments

Comments
 (0)