Skip to content

Commit f863870

Browse files
MONGOID-5098 Bug fix: Range.mongoize should mongoize its members (mongodb#5108)
* Simple fix for Range mongoize * Code cleanup Co-authored-by: shields <[email protected]>
1 parent 19494e0 commit f863870

File tree

3 files changed

+410
-65
lines changed

3 files changed

+410
-65
lines changed

lib/mongoid/extensions/range.rb

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,30 @@ def demongoize(object)
5959
#
6060
# @return [ Hash ] The object mongoized.
6161
def mongoize(object)
62-
return nil if object.nil?
63-
return object if object.is_a?(::Hash)
64-
return object if object.is_a?(String)
65-
hash = { "min" => object.first, "max" => object.last }
62+
case object
63+
when NilClass then nil
64+
when String then object
65+
when Hash then __mongoize_hash__(object)
66+
else __mongoize_range__(object)
67+
end
68+
end
69+
70+
private
71+
72+
def __mongoize_hash__(object)
73+
hash = object.stringify_keys
74+
hash.slice!('min', 'max', 'exclude_end')
75+
hash.compact!
76+
hash.transform_values!(&:mongoize)
77+
hash
78+
end
79+
80+
def __mongoize_range__(object)
81+
hash = {}
82+
hash['min'] = object.first&.mongoize
83+
hash['max'] = object.last&.mongoize
6684
if object.respond_to?(:exclude_end?) && object.exclude_end?
67-
hash.merge!("exclude_end" => true)
85+
hash['exclude_end'] = true
6886
end
6987
hash
7088
end
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'Range field persistence' do
6+
let!(:person) { Person.create!(field => value).reload }
7+
subject { person.send(field) }
8+
let(:now_utc) { Time.now }
9+
let(:later_utc) { now_utc + 10.minutes }
10+
let(:now_in_zone) { now_utc.in_time_zone('Asia/Tokyo') }
11+
let(:later_in_zone) { later_utc.in_time_zone('Asia/Tokyo') }
12+
13+
context 'static field' do
14+
let(:field) { :range }
15+
16+
context 'when Integer' do
17+
let(:value) { 1..3 }
18+
it do
19+
expect(subject).to eq(1..3)
20+
end
21+
end
22+
23+
context 'when Integer exclude_end' do
24+
let(:value) { 1...3 }
25+
it { expect(subject).to eq(1...3) }
26+
end
27+
28+
context 'when Hash<String, Integer>' do
29+
let(:value) { { 'min' => 1, 'max' => 3 } }
30+
it { expect(subject).to eq(1..3) }
31+
end
32+
33+
context 'when Hash<String, Integer> exclude_end' do
34+
let(:value) { { 'min' => 1, 'max' => 3, 'exclude_end' => true } }
35+
it { expect(subject).to eq(1...3) }
36+
end
37+
38+
context 'when Hash<Symbol, Integer>' do
39+
let(:value) { { min: 1, max: 3 } }
40+
it { expect(subject).to eq(1..3) }
41+
end
42+
43+
context 'when Hash<Symbol, Integer> exclude_end' do
44+
let(:value) { { min: 1, max: 3, exclude_end: true } }
45+
it { expect(subject).to eq(1...3) }
46+
end
47+
48+
context 'when Time' do
49+
let(:value) { now_utc..later_utc }
50+
51+
it do
52+
expect(subject).to be_a Range
53+
expect(subject.exclude_end?).to eq false
54+
expect(subject.first).to be_within(0.01.second).of(now_utc)
55+
expect(subject.last).to be_within(0.01.second).of(later_utc)
56+
expect(subject.first.class).to eq Time
57+
expect(subject.last.class).to eq Time
58+
end
59+
end
60+
61+
context 'when Time exclude_end' do
62+
let(:value) { now_utc...later_utc }
63+
64+
it do
65+
expect(subject).to be_a Range
66+
expect(subject.exclude_end?).to eq true
67+
expect(subject.first).to be_within(0.01.second).of(now_utc)
68+
expect(subject.last).to be_within(0.01.second).of(later_utc)
69+
expect(subject.first.class).to eq Time
70+
expect(subject.last.class).to eq Time
71+
end
72+
end
73+
74+
context 'when Hash<String, Time>' do
75+
let(:value) { { 'min' => now_utc, 'max' => later_utc } }
76+
77+
it do
78+
expect(subject).to be_a Range
79+
expect(subject.exclude_end?).to eq false
80+
expect(subject.first).to be_within(0.01.second).of(now_utc)
81+
expect(subject.last).to be_within(0.01.second).of(later_utc)
82+
expect(subject.first.class).to eq Time
83+
expect(subject.last.class).to eq Time
84+
end
85+
end
86+
87+
context 'when Hash<String, Time> exclude_end' do
88+
let(:value) { { 'min' => now_utc, 'max' => later_utc, 'exclude_end' => true } }
89+
90+
it do
91+
expect(subject).to be_a Range
92+
expect(subject.exclude_end?).to eq true
93+
expect(subject.first).to be_within(0.01.second).of(now_utc)
94+
expect(subject.last).to be_within(0.01.second).of(later_utc)
95+
expect(subject.first.class).to eq Time
96+
expect(subject.last.class).to eq Time
97+
end
98+
end
99+
100+
context 'when ActiveSupport::TimeWithZone' do
101+
let(:value) { now_in_zone..later_in_zone }
102+
103+
it do
104+
expect(subject).to be_a Range
105+
expect(subject.exclude_end?).to eq false
106+
expect(subject.first).to be_within(0.01.second).of(now_utc)
107+
expect(subject.last).to be_within(0.01.second).of(later_utc)
108+
expect(subject.first.class).to eq Time
109+
expect(subject.last.class).to eq Time
110+
end
111+
end
112+
113+
context 'when ActiveSupport::TimeWithZone exclude_end' do
114+
let(:value) { now_in_zone...later_in_zone }
115+
116+
it do
117+
expect(subject).to be_a Range
118+
expect(subject.exclude_end?).to eq true
119+
expect(subject.first).to be_within(0.01.second).of(now_utc)
120+
expect(subject.last).to be_within(0.01.second).of(later_utc)
121+
expect(subject.first.class).to eq Time
122+
expect(subject.last.class).to eq Time
123+
end
124+
end
125+
126+
context 'when Hash<String, ActiveSupport::TimeWithZone>' do
127+
let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone } }
128+
129+
it do
130+
expect(subject).to be_a Range
131+
expect(subject.exclude_end?).to eq false
132+
expect(subject.first).to be_within(0.01.second).of(now_utc)
133+
expect(subject.last).to be_within(0.01.second).of(later_utc)
134+
expect(subject.first.class).to eq Time
135+
expect(subject.last.class).to eq Time
136+
end
137+
end
138+
139+
context 'when Hash<String, ActiveSupport::TimeWithZone> exclude_end' do
140+
let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone, 'exclude_end' => true } }
141+
142+
it do
143+
expect(subject).to be_a Range
144+
expect(subject.exclude_end?).to eq true
145+
expect(subject.first).to be_within(0.01.second).of(now_utc)
146+
expect(subject.last).to be_within(0.01.second).of(later_utc)
147+
expect(subject.first.class).to eq Time
148+
expect(subject.last.class).to eq Time
149+
end
150+
end
151+
end
152+
153+
context 'dynamic field' do
154+
let(:field) { :dynamic }
155+
156+
context 'when Integer' do
157+
let(:value) { 1..3 }
158+
it do
159+
expect(subject).to eq('max' => 3, 'min' => 1)
160+
end
161+
end
162+
163+
context 'when Integer exclude_end' do
164+
let(:value) { 1...3 }
165+
it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) }
166+
end
167+
168+
context 'when descending' do
169+
let(:value) { 3..1 }
170+
it { expect(subject).to eq('max' => 1, 'min' => 3) }
171+
end
172+
173+
context 'when descending exclude_end' do
174+
let(:value) { 3...1 }
175+
it { expect(subject).to eq('max' => 1, 'min' => 3, 'exclude_end' => true) }
176+
end
177+
178+
context 'when Hash<String, Integer>' do
179+
let(:value) { { 'min' => 1, 'max' => 3 } }
180+
it { expect(subject).to eq('max' => 3, 'min' => 1) }
181+
end
182+
183+
context 'when Hash<String, Integer> exclude_end' do
184+
let(:value) { { 'min' => 1, 'max' => 3, 'exclude_end' => true } }
185+
it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) }
186+
end
187+
188+
context 'when Hash<Symbol, Integer>' do
189+
let(:value) { { min: 1, max: 3 } }
190+
it { expect(subject).to eq('max' => 3, 'min' => 1) }
191+
end
192+
193+
context 'when Hash<Symbol, Integer> exclude_end' do
194+
let(:value) { { min: 1, max: 3, exclude_end: true } }
195+
it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) }
196+
end
197+
198+
context 'when Time' do
199+
let(:value) { now_utc..later_utc }
200+
201+
it do
202+
expect(subject).to be_a Hash
203+
expect(subject['exclude_end']).to eq nil
204+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
205+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
206+
expect(subject['min'].class).to eq Time
207+
expect(subject['max'].class).to eq Time
208+
end
209+
end
210+
211+
context 'when Time exclude_end' do
212+
let(:value) { now_utc...later_utc }
213+
214+
it do
215+
expect(subject).to be_a Hash
216+
expect(subject['exclude_end']).to eq true
217+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
218+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
219+
expect(subject['min'].class).to eq Time
220+
expect(subject['max'].class).to eq Time
221+
end
222+
end
223+
224+
context 'when Hash<String, Time>' do
225+
let(:value) { { 'min' => now_utc, 'max' => later_utc } }
226+
227+
it do
228+
expect(subject).to be_a Hash
229+
expect(subject['exclude_end']).to eq nil
230+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
231+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
232+
expect(subject['min'].class).to eq Time
233+
expect(subject['max'].class).to eq Time
234+
end
235+
end
236+
237+
context 'when Hash<String, Time> exclude_end' do
238+
let(:value) { { 'min' => now_utc, 'max' => later_utc, 'exclude_end' => true } }
239+
240+
it do
241+
expect(subject).to be_a Hash
242+
expect(subject['exclude_end']).to eq true
243+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
244+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
245+
expect(subject['min'].class).to eq Time
246+
expect(subject['max'].class).to eq Time
247+
end
248+
end
249+
250+
context 'when ActiveSupport::TimeWithZone' do
251+
let(:value) { now_in_zone..later_in_zone }
252+
253+
it do
254+
expect(subject).to be_a Hash
255+
expect(subject['exclude_end']).to eq nil
256+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
257+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
258+
expect(subject['min'].class).to eq Time
259+
expect(subject['max'].class).to eq Time
260+
end
261+
end
262+
263+
context 'when ActiveSupport::TimeWithZone exclude_end' do
264+
let(:value) { now_in_zone...later_in_zone }
265+
266+
it do
267+
expect(subject).to be_a Hash
268+
expect(subject['exclude_end']).to eq true
269+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
270+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
271+
expect(subject['min'].class).to eq Time
272+
expect(subject['max'].class).to eq Time
273+
end
274+
end
275+
276+
context 'when Hash<String, ActiveSupport::TimeWithZone>' do
277+
let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone } }
278+
279+
it do
280+
expect(subject).to be_a Hash
281+
expect(subject['exclude_end']).to eq nil
282+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
283+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
284+
expect(subject['min'].class).to eq Time
285+
expect(subject['max'].class).to eq Time
286+
end
287+
end
288+
289+
context 'when Hash<String, ActiveSupport::TimeWithZone> exclude_end' do
290+
let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone, 'exclude_end' => true } }
291+
292+
it do
293+
expect(subject).to be_a Hash
294+
expect(subject['exclude_end']).to eq true
295+
expect(subject['min']).to be_within(0.01.second).of(now_utc)
296+
expect(subject['max']).to be_within(0.01.second).of(later_utc)
297+
expect(subject['min'].class).to eq Time
298+
expect(subject['max'].class).to eq Time
299+
end
300+
end
301+
end
302+
end

0 commit comments

Comments
 (0)