Skip to content

Commit 67c8035

Browse files
johnnyshieldsjamis
andauthored
MONGOID-5665 [Monkey Patch Removal] Remove Object#__find_args__ (#5706)
* Remove Object#__find_args__. This primarily used in Criteria::Findable where it is now a private method. 2 points to note: - __find_args__ was mistakenly called in Atomic#unset method. Although technically __find_args__ does a similar thing as what #unset requires for its argument preparation, this is pure coincidence and its a kludge to use find_args here. So I've inlined the code and made a new private method. - __find_args__ for Range previously called to_a. This is actually a bug / DOS risk, because ranges of strings tend to explode when you use to_a. As an example, ('64e0'..'64ff').to_a.size => 9320, and it gets much worse for 24-char BSON ids! Interestingly however, MongoDB itself can handle ranges of ids gracefully, so I am now just passing them into the DB as-is and it magically works--I've added tests. * Fix specs * Add test to cover #find for nested arrays, which was pre-existing behavior. * deprecate __find_args__ instead of removing it * Set#resizable? has to be true for this to work --------- Co-authored-by: Jamis Buck <[email protected]>
1 parent 20c6563 commit 67c8035

File tree

9 files changed

+257
-34
lines changed

9 files changed

+257
-34
lines changed

lib/mongoid/contextual/atomic.rb

+25-9
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,14 @@ def set(sets)
169169
# @example Unset the field on the matches.
170170
# context.unset(:name)
171171
#
172-
# @param [ [ String | Symbol | Array<String | Symbol> | Hash ]... ] *args
173-
# The name(s) of the field(s) to unset.
174-
# If a Hash is specified, its keys will be used irrespective of what
175-
# each key's value is, even if the value is nil or false.
172+
# @param [ [ String | Symbol | Array<String | Symbol> | Hash ]... ] *unsets
173+
# The name(s) of the field(s) to unset. If a Hash is specified,
174+
# its keys will be used irrespective of value, even if the value
175+
# is nil or false.
176176
#
177177
# @return [ nil ] Nil.
178-
def unset(*args)
179-
fields = args.map { |a| a.is_a?(Hash) ? a.keys : a }
180-
.__find_args__
181-
.map { |f| [database_field_name(f), true] }
182-
view.update_many("$unset" => Hash[fields])
178+
def unset(*unsets)
179+
view.update_many('$unset' => collect_unset_operations(unsets))
183180
end
184181

185182
# Performs an atomic $min update operation on the given field or fields.
@@ -247,6 +244,25 @@ def collect_each_operations(ops)
247244
operations[database_field_name(field)] = { "$each" => Array.wrap(value).mongoize }
248245
end
249246
end
247+
248+
# Builds the selector an atomic $unset operation from arguments.
249+
#
250+
# @example Prepare selector from array.
251+
# context.collect_unset_operations([:name, :age])
252+
# #=> { "name" => true, "age" => true }
253+
#
254+
# @example Prepare selector from hash.
255+
# context.collect_unset_operations({ name: 1 }, { age: 1 })
256+
# #=> { "name" => true, "age" => true }
257+
#
258+
# @param [ String | Symbol | Array<String | Symbol> | Hash ] ops
259+
# The name(s) of the field(s) to unset.
260+
#
261+
# @return [ Hash ] The selector for the atomic $unset operation.
262+
def collect_unset_operations(ops)
263+
ops.map { |op| op.is_a?(Hash) ? op.keys : op }.flatten
264+
.map { |field| [database_field_name(field), true] }.to_h
265+
end
250266
end
251267
end
252268
end

lib/mongoid/criteria/findable.rb

+22-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def execute_or_raise(ids, multi)
4141
#
4242
# @return [ Document | Array<Document> ] The matching document(s).
4343
def find(*args)
44-
ids = args.__find_args__
44+
ids = prepare_ids_for_find(args)
4545
raise_invalid if ids.any?(&:nil?)
4646
for_ids(ids).execute_or_raise(ids, multi_args?(args))
4747
end
@@ -134,6 +134,27 @@ def mongoize_ids(ids)
134134
end
135135
end
136136

137+
# Convert args to the +#find+ method into a flat array of ids.
138+
#
139+
# @example Get the ids.
140+
# prepare_ids_for_find([ 1, [ 2, 3 ] ])
141+
#
142+
# @param [ Array<Object> ] args The arguments.
143+
#
144+
# @return [ Array ] The array of ids.
145+
def prepare_ids_for_find(args)
146+
args.flat_map do |arg|
147+
case arg
148+
when Array, Set
149+
prepare_ids_for_find(arg)
150+
when Range
151+
arg.begin&.numeric? && arg.end&.numeric? ? arg.to_a : arg
152+
else
153+
arg
154+
end
155+
end.uniq(&:to_s)
156+
end
157+
137158
# Indicates whether the given arguments array is a list of values.
138159
# Used by the +find+ method to determine whether to return an array
139160
# or single value.

lib/mongoid/extensions/array.rb

+2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ def __evolve_object_id__
2323
# [ 1, 2, 3 ].__find_args__
2424
#
2525
# @return [ Array ] The array of args.
26+
# @deprecated
2627
def __find_args__
2728
flat_map{ |a| a.__find_args__ }.uniq{ |a| a.to_s }
2829
end
30+
Mongoid.deprecate(self, :__find_args__)
2931

3032
# Mongoize the array into an array of object ids.
3133
#

lib/mongoid/extensions/object.rb

+2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ def __evolve_object_id__
2424
# object.__find_args__
2525
#
2626
# @return [ Object ] self.
27+
# @deprecated
2728
def __find_args__
2829
self
2930
end
31+
Mongoid.deprecate(self, :__find_args__)
3032

3133
# Mongoize a plain object into a time.
3234
#

lib/mongoid/extensions/range.rb

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33

44
module Mongoid
55
module Extensions
6-
76
# Adds type-casting behavior to Range class.
87
module Range
8+
def self.included(base)
9+
base.extend(ClassMethods)
10+
end
911

1012
# Get the range as arguments for a find.
1113
#
1214
# @example Get the range as find args.
1315
# range.__find_args__
1416
#
1517
# @return [ Array ] The range as an array.
18+
# @deprecated
1619
def __find_args__
1720
to_a
1821
end
22+
Mongoid.deprecate(self, :__find_args__)
1923

2024
# Turn the object from the ruby type we deal with to a Mongo friendly
2125
# type.
@@ -39,7 +43,6 @@ def resizable?
3943
end
4044

4145
module ClassMethods
42-
4346
# Convert the object from its mongo friendly ruby type to this type.
4447
#
4548
# @example Demongoize the object.
@@ -107,5 +110,4 @@ def __mongoize_range__(object)
107110
end
108111
end
109112

110-
::Range.__send__(:include, Mongoid::Extensions::Range)
111-
::Range.extend(Mongoid::Extensions::Range::ClassMethods)
113+
Range.include(Mongoid::Extensions::Range)

lib/mongoid/extensions/set.rb

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ module Extensions
66

77
# Adds type-casting behavior to Set class.
88
module Set
9-
109
# Turn the object from the ruby type we deal with to a Mongo friendly
1110
# type.
1211
#
@@ -18,8 +17,17 @@ def mongoize
1817
::Set.mongoize(self)
1918
end
2019

21-
module ClassMethods
20+
# Returns whether the object's size can be changed.
21+
#
22+
# @example Is the object resizable?
23+
# object.resizable?
24+
#
25+
# @return [ true ] true.
26+
def resizable?
27+
true
28+
end
2229

30+
module ClassMethods
2331
# Convert the object from its mongo friendly ruby type to this type.
2432
#
2533
# @example Demongoize the object.

spec/mongoid/criteria/findable_spec.rb

+190
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,196 @@
260260
end
261261
end
262262

263+
context "when providing nested arrays of ids" do
264+
265+
let!(:band_two) do
266+
Band.create!(name: "Tool")
267+
end
268+
269+
context "when all ids match" do
270+
271+
let(:found) do
272+
Band.find([ [ band.id ], [ [ band_two.id ] ] ])
273+
end
274+
275+
it "contains the first match" do
276+
expect(found).to include(band)
277+
end
278+
279+
it "contains the second match" do
280+
expect(found).to include(band_two)
281+
end
282+
283+
context "when ids are duplicates" do
284+
285+
let(:found) do
286+
Band.find([ [ band.id ], [ [ band.id ] ] ])
287+
end
288+
289+
it "contains only the first match" do
290+
expect(found).to eq([band])
291+
end
292+
end
293+
end
294+
295+
context "when any id does not match" do
296+
297+
context "when raising a not found error" do
298+
config_override :raise_not_found_error, true
299+
300+
let(:found) do
301+
Band.find([ [ band.id ], [ [ BSON::ObjectId.new ] ] ])
302+
end
303+
304+
it "raises an error" do
305+
expect {
306+
found
307+
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document\(s\) not found for class Band with id\(s\)/)
308+
end
309+
end
310+
311+
context "when raising no error" do
312+
config_override :raise_not_found_error, false
313+
314+
let(:found) do
315+
Band.find([ [ band.id ], [ [ BSON::ObjectId.new ] ] ])
316+
end
317+
318+
it "returns only the matching documents" do
319+
expect(found).to eq([ band ])
320+
end
321+
end
322+
end
323+
end
324+
325+
context "when providing a Set of ids" do
326+
327+
let!(:band_two) do
328+
Band.create!(name: "Tool")
329+
end
330+
331+
context "when all ids match" do
332+
let(:found) do
333+
Band.find(Set[ band.id, band_two.id ])
334+
end
335+
336+
it "contains the first match" do
337+
expect(found).to include(band)
338+
end
339+
340+
it "contains the second match" do
341+
expect(found).to include(band_two)
342+
end
343+
end
344+
345+
context "when any id does not match" do
346+
347+
context "when raising a not found error" do
348+
config_override :raise_not_found_error, true
349+
350+
let(:found) do
351+
Band.find(Set[ band.id, BSON::ObjectId.new ])
352+
end
353+
354+
it "raises an error" do
355+
expect {
356+
found
357+
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document\(s\) not found for class Band with id\(s\)/)
358+
end
359+
end
360+
361+
context "when raising no error" do
362+
config_override :raise_not_found_error, false
363+
364+
let(:found) do
365+
Band.find(Set[ band.id, BSON::ObjectId.new ])
366+
end
367+
368+
it "returns only the matching documents" do
369+
expect(found).to eq([ band ])
370+
end
371+
end
372+
end
373+
end
374+
375+
context "when providing a Range of ids" do
376+
377+
let!(:band_two) do
378+
Band.create!(name: "Tool")
379+
end
380+
381+
context "when all ids match" do
382+
let(:found) do
383+
Band.find(band.id.to_s..band_two.id.to_s)
384+
end
385+
386+
it "contains the first match" do
387+
expect(found).to include(band)
388+
end
389+
390+
it "contains the second match" do
391+
expect(found).to include(band_two)
392+
end
393+
394+
395+
context "when any id does not match" do
396+
397+
context "when raising a not found error" do
398+
config_override :raise_not_found_error, true
399+
400+
let(:found) do
401+
Band.find(band_two.id.to_s..BSON::ObjectId.new)
402+
end
403+
404+
it "does not raise error and returns only the matching documents" do
405+
expect(found).to eq([ band_two ])
406+
end
407+
end
408+
409+
context "when raising no error" do
410+
config_override :raise_not_found_error, false
411+
412+
let(:found) do
413+
Band.find(band_two.id.to_s..BSON::ObjectId.new)
414+
end
415+
416+
it "returns only the matching documents" do
417+
expect(found).to eq([ band_two ])
418+
end
419+
end
420+
end
421+
end
422+
423+
context "when all ids do not match" do
424+
425+
context "when raising a not found error" do
426+
config_override :raise_not_found_error, true
427+
428+
let(:found) do
429+
Band.find(BSON::ObjectId.new..BSON::ObjectId.new)
430+
end
431+
432+
it "raises an error" do
433+
expect {
434+
found
435+
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document\(s\) not found for class Band with id\(s\)/)
436+
end
437+
end
438+
439+
context "when raising no error" do
440+
config_override :raise_not_found_error, false
441+
442+
let(:found) do
443+
Band.find(BSON::ObjectId.new..BSON::ObjectId.new)
444+
end
445+
446+
it "returns only the matching documents" do
447+
expect(found).to eq([])
448+
end
449+
end
450+
end
451+
end
452+
263453
context "when providing a single id as extended json" do
264454

265455
context "when the id matches" do

spec/mongoid/extensions/object_spec.rb

-7
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616
end
1717
end
1818

19-
describe "#__find_args__" do
20-
21-
it "returns self" do
22-
expect(object.__find_args__).to eq(object)
23-
end
24-
end
25-
2619
describe "#__mongoize_object_id__" do
2720

2821
it "returns self" do

0 commit comments

Comments
 (0)