Skip to content

Commit 0a65cc0

Browse files
johnnyshieldscomandeo-mongop
authored
MONGOID-5098 Range.mongoize should support end-less and begin-less ranges (#5026)
* Standardize mongoization of range * demongoize invalid values to nil * fix date test * fix syntax for old rubies * fix tests Co-authored-by: shields <shields@tablecheck.com> Co-authored-by: Dmitry Rybakov <dmitry.rybakov@mongodb.com> Co-authored-by: Oleg Pudeyev <code@olegp.name>
1 parent 5c44293 commit 0a65cc0

File tree

3 files changed

+177
-5
lines changed

3 files changed

+177
-5
lines changed

lib/mongoid/extensions/range.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ module ClassMethods
4444
#
4545
# @param [ Hash ] object The object to demongoize.
4646
#
47-
# @return [ Range ] The range.
47+
# @return [ Range | nil ] The range, or nil if object cannot be represented as range.
48+
#
49+
# @note Ruby 2.6 and lower do not support endless ranges that Ruby 2.7+ support.
4850
def demongoize(object)
4951
object.nil? ? nil : ::Range.new(object["min"], object["max"], object["exclude_end"])
52+
rescue ArgumentError # can be removed when Ruby version >= 2.7
53+
nil
5054
end
5155

5256
# Turn the object from the ruby type we deal with to a Mongo friendly
@@ -79,8 +83,8 @@ def __mongoize_hash__(object)
7983

8084
def __mongoize_range__(object)
8185
hash = {}
82-
hash['min'] = object.first&.mongoize
83-
hash['max'] = object.last&.mongoize
86+
hash['min'] = object.begin.mongoize if object.begin
87+
hash['max'] = object.end.mongoize if object.end
8488
if object.respond_to?(:exclude_end?) && object.exclude_end?
8589
hash['exclude_end'] = true
8690
end

spec/integration/persistence/range_field_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@
2525
it { expect(subject).to eq(1...3) }
2626
end
2727

28+
context 'when endless' do
29+
ruby_version_gte '2.6'
30+
let(:value) { eval('3..') }
31+
it { expect(subject).to eq(eval('3..')) }
32+
end
33+
34+
context 'when endless exclude_end' do
35+
ruby_version_gte '2.6'
36+
let(:value) { eval('3...') }
37+
it { expect(subject).to eq(eval('3...')) }
38+
end
39+
40+
context 'when beginning-less' do
41+
ruby_version_gte '2.7'
42+
let(:value) { eval('..3') }
43+
it { expect(subject).to eq(eval('..3')) }
44+
end
45+
46+
context 'when beginning-less exclude_end' do
47+
ruby_version_gte '2.7'
48+
let(:value) { eval('...3') }
49+
it { expect(subject).to eq(eval('...3')) }
50+
end
51+
2852
context 'when Hash<String, Integer>' do
2953
let(:value) { { 'min' => 1, 'max' => 3 } }
3054
it { expect(subject).to eq(1..3) }
@@ -175,6 +199,30 @@
175199
it { expect(subject).to eq('max' => 1, 'min' => 3, 'exclude_end' => true) }
176200
end
177201

202+
context 'when endless' do
203+
ruby_version_gte '2.6'
204+
let(:value) { eval('3..') }
205+
it { expect(subject).to eq('min' => 3) }
206+
end
207+
208+
context 'when endless exclude_end' do
209+
ruby_version_gte '2.6'
210+
let(:value) { eval('3...') }
211+
it { expect(subject).to eq('min' => 3, 'exclude_end' => true) }
212+
end
213+
214+
context 'when beginning-less' do
215+
ruby_version_gte '2.7'
216+
let(:value) { eval('..3') }
217+
it { expect(subject).to eq('max' => 3) }
218+
end
219+
220+
context 'when beginning-less exclude_end' do
221+
ruby_version_gte '2.7'
222+
let(:value) { eval('...3') }
223+
it { expect(subject).to eq('max' => 3, 'exclude_end' => true) }
224+
end
225+
178226
context 'when Hash<String, Integer>' do
179227
let(:value) { { 'min' => 1, 'max' => 3 } }
180228
it { expect(subject).to eq('max' => 3, 'min' => 1) }

spec/mongoid/extensions/range_spec.rb

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,86 @@
6565
is_expected.to eq("a"..."z")
6666
end
6767
end
68+
69+
context "when the range is endless" do
70+
let(:hash) { { "min" => 1, "max" => nil } }
71+
72+
context 'kernel can support endless range' do
73+
ruby_version_gte '2.6'
74+
75+
it "returns an alphabetic range" do
76+
is_expected.to eq(eval('1..'))
77+
end
78+
end
79+
80+
context 'kernel cannot support endless range' do
81+
ruby_version_lt '2.6'
82+
83+
it "returns nil" do
84+
is_expected.to be nil
85+
end
86+
end
87+
end
88+
89+
context "when the range is endless with exclude end" do
90+
let(:hash) { { "min" => 1, "max" => nil, "exclude_end" => true } }
91+
92+
context 'kernel can support endless range' do
93+
ruby_version_gte '2.6'
94+
95+
it "returns an alphabetic range" do
96+
is_expected.to eq(eval('1...'))
97+
end
98+
end
99+
100+
context 'kernel cannot support endless range' do
101+
ruby_version_lt '2.6'
102+
103+
it "returns nil" do
104+
is_expected.to be nil
105+
end
106+
end
107+
end
108+
109+
context "when the range is beginning-less" do
110+
let(:hash) { { "min" => nil, "max" => 3 } }
111+
112+
context 'kernel can support beginning-less range' do
113+
ruby_version_gte '2.7'
114+
115+
it "returns an alphabetic range" do
116+
is_expected.to eq(nil..3)
117+
end
118+
end
119+
120+
context 'kernel cannot support beginning-less range' do
121+
ruby_version_lt '2.7'
122+
123+
it "returns nil" do
124+
is_expected.to be nil
125+
end
126+
end
127+
end
128+
129+
context "when the range is beginning-less with exclude end" do
130+
let(:hash) { { "min" => nil, "max" => 3, "exclude_end" => true } }
131+
132+
context 'kernel can support endless range' do
133+
ruby_version_gte '2.7'
134+
135+
it "returns an alphabetic beginning-less" do
136+
is_expected.to eq(eval('...3'))
137+
end
138+
end
139+
140+
context 'kernel cannot support beginning-less range' do
141+
ruby_version_lt '2.7'
142+
143+
it "returns nil" do
144+
is_expected.to be nil
145+
end
146+
end
147+
end
68148
end
69149

70150
shared_examples_for 'mongoize range' do
@@ -101,6 +181,46 @@
101181
end
102182
end
103183

184+
context 'given an endless range' do
185+
ruby_version_gte '2.6'
186+
187+
let(:range) { eval('5..') }
188+
189+
it "returns the object hash" do
190+
is_expected.to eq("min" => 5)
191+
end
192+
end
193+
194+
context 'given an endless range not inclusive' do
195+
ruby_version_gte '2.6'
196+
197+
let(:range) { eval('5...') }
198+
199+
it "returns the object hash" do
200+
is_expected.to eq("min" => 5, "exclude_end" => true)
201+
end
202+
end
203+
204+
context 'given a beginning-less range' do
205+
ruby_version_gte '2.7'
206+
207+
let(:range) { eval('..5') }
208+
209+
it "returns the object hash" do
210+
is_expected.to eq("max" => 5)
211+
end
212+
end
213+
214+
context 'given an endless range not inclusive' do
215+
ruby_version_gte '2.7'
216+
217+
let(:range) { eval('...5') }
218+
219+
it "returns the object hash" do
220+
is_expected.to eq("max" => 5, "exclude_end" => true)
221+
end
222+
end
223+
104224
context 'given a letter range' do
105225
let(:range) { 'a'..'z' }
106226

@@ -138,10 +258,10 @@
138258
end
139259

140260
context 'given a Date range' do
141-
let(:range) { Time.at(0).to_date..Time.at(1).to_date }
261+
let(:range) { Date.new(2020, 1, 1)..Date.new(2020, 1, 2) }
142262

143263
it "returns the object hash" do
144-
is_expected.to eq("min" => Time.at(0).in_time_zone, "max" => Time.at(0).in_time_zone)
264+
is_expected.to eq("min" => Time.utc(2020, 1, 1), "max" => Time.utc(2020, 1, 2))
145265
expect(subject["min"].utc?).to be(true)
146266
expect(subject["max"].utc?).to be(true)
147267
end

0 commit comments

Comments
 (0)