plucky 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/UPGRADES +2 -0
- data/lib/plucky.rb +17 -9
- data/lib/plucky/new_relic.rb +9 -0
- data/lib/plucky/query.rb +9 -3
- data/lib/plucky/version.rb +2 -2
- data/test/helper.rb +45 -0
- data/test/plucky/pagination/test_decorator.rb +34 -0
- data/test/plucky/pagination/test_paginator.rb +120 -0
- data/test/plucky/test_criteria_hash.rb +296 -0
- data/test/plucky/test_options_hash.rb +302 -0
- data/test/plucky/test_query.rb +819 -0
- data/test/test_plucky.rb +46 -0
- data/test/test_symbol.rb +11 -0
- data/test/test_symbol_operator.rb +49 -0
- metadata +18 -6
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class OptionsHashTest < Test::Unit::TestCase
|
4
|
+
include Plucky
|
5
|
+
|
6
|
+
context "Plucky::OptionsHash" do
|
7
|
+
should "delegate missing methods to the source hash" do
|
8
|
+
hash = {:limit => 1, :skip => 1}
|
9
|
+
options = OptionsHash.new(hash)
|
10
|
+
options[:skip].should == 1
|
11
|
+
options[:limit].should == 1
|
12
|
+
options.keys.should == [:limit, :skip]
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#initialize_copy" do
|
16
|
+
setup do
|
17
|
+
@original = OptionsHash.new(:fields => {:name => true}, :sort => :name, :limit => 10)
|
18
|
+
@cloned = @original.clone
|
19
|
+
end
|
20
|
+
|
21
|
+
should "duplicate source hash" do
|
22
|
+
@cloned.source.should_not equal(@original.source)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "clone duplicable? values" do
|
26
|
+
@cloned[:fields].should_not equal(@original[:fields])
|
27
|
+
@cloned[:sort].should_not equal(@original[:sort])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#fields?" do
|
32
|
+
should "be true if fields have been selected" do
|
33
|
+
OptionsHash.new(:fields => :name).fields?.should be(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "be false if no fields have been selected" do
|
37
|
+
OptionsHash.new.fields?.should be(false)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#[]=" do
|
42
|
+
should "convert order to sort" do
|
43
|
+
options = OptionsHash.new(:order => :foo)
|
44
|
+
options[:order].should be_nil
|
45
|
+
options[:sort].should == [['foo', 1]]
|
46
|
+
end
|
47
|
+
|
48
|
+
should "convert select to fields" do
|
49
|
+
options = OptionsHash.new(:select => 'foo')
|
50
|
+
options[:select].should be_nil
|
51
|
+
options[:fields].should == ['foo']
|
52
|
+
end
|
53
|
+
|
54
|
+
should "convert offset to skip" do
|
55
|
+
options = OptionsHash.new(:offset => 1)
|
56
|
+
options[:offset].should be_nil
|
57
|
+
options[:skip].should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
context ":fields" do
|
61
|
+
setup { @options = OptionsHash.new }
|
62
|
+
subject { @options }
|
63
|
+
|
64
|
+
should "default to nil" do
|
65
|
+
subject[:fields].should be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
should "be nil if empty string" do
|
69
|
+
subject[:fields] = ''
|
70
|
+
subject[:fields].should be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
should "be nil if empty array" do
|
74
|
+
subject[:fields] = []
|
75
|
+
subject[:fields].should be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
should "work with array" do
|
79
|
+
subject[:fields] = %w[one two]
|
80
|
+
subject[:fields].should == %w[one two]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Ruby 1.9.1 was sending array [{:age => 20}],
|
84
|
+
# instead of hash.
|
85
|
+
should "work with array that has one hash" do
|
86
|
+
subject[:fields] = [{:age => 20}]
|
87
|
+
subject[:fields].should == {:age => 20}
|
88
|
+
end
|
89
|
+
|
90
|
+
should "flatten multi-dimensional array" do
|
91
|
+
subject[:fields] = [[:one, :two]]
|
92
|
+
subject[:fields].should == [:one, :two]
|
93
|
+
end
|
94
|
+
|
95
|
+
should "work with symbol" do
|
96
|
+
subject[:fields] = :one
|
97
|
+
subject[:fields].should == [:one]
|
98
|
+
end
|
99
|
+
|
100
|
+
should "work with array of symbols" do
|
101
|
+
subject[:fields] = [:one, :two]
|
102
|
+
subject[:fields].should == [:one, :two]
|
103
|
+
end
|
104
|
+
|
105
|
+
should "work with hash" do
|
106
|
+
subject[:fields] = {:one => 1, :two => -1}
|
107
|
+
subject[:fields].should == {:one => 1, :two => -1}
|
108
|
+
end
|
109
|
+
|
110
|
+
should "convert comma separated list to array" do
|
111
|
+
subject[:fields] = 'one, two'
|
112
|
+
subject[:fields].should == %w[one two]
|
113
|
+
end
|
114
|
+
|
115
|
+
should "convert select" do
|
116
|
+
subject[:select] = 'one, two'
|
117
|
+
subject[:select].should be_nil
|
118
|
+
subject[:fields].should == %w[one two]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context ":limit" do
|
123
|
+
setup { @options = OptionsHash.new }
|
124
|
+
subject { @options }
|
125
|
+
|
126
|
+
should "default to nil" do
|
127
|
+
subject[:limit].should be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
should "use limit provided" do
|
131
|
+
subject[:limit] = 1
|
132
|
+
subject[:limit].should == 1
|
133
|
+
end
|
134
|
+
|
135
|
+
should "convert string to integer" do
|
136
|
+
subject[:limit] = '1'
|
137
|
+
subject[:limit].should == 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context ":skip" do
|
142
|
+
setup { @options = OptionsHash.new }
|
143
|
+
subject { @options }
|
144
|
+
|
145
|
+
should "default to nil" do
|
146
|
+
subject[:skip].should be_nil
|
147
|
+
end
|
148
|
+
|
149
|
+
should "use limit provided" do
|
150
|
+
subject[:skip] = 1
|
151
|
+
subject[:skip].should == 1
|
152
|
+
end
|
153
|
+
|
154
|
+
should "convert string to integer" do
|
155
|
+
subject[:skip] = '1'
|
156
|
+
subject[:skip].should == 1
|
157
|
+
end
|
158
|
+
|
159
|
+
should "be set from offset" do
|
160
|
+
subject[:offset] = '1'
|
161
|
+
subject[:offset].should be_nil
|
162
|
+
subject[:skip].should == 1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context ":sort" do
|
167
|
+
setup { @options = OptionsHash.new }
|
168
|
+
subject { @options }
|
169
|
+
|
170
|
+
should "default to nil" do
|
171
|
+
subject[:sort].should be_nil
|
172
|
+
end
|
173
|
+
|
174
|
+
should "work with natural order ascending" do
|
175
|
+
subject[:sort] = {'$natural' => 1}
|
176
|
+
subject[:sort].should == {'$natural' => 1}
|
177
|
+
end
|
178
|
+
|
179
|
+
should "work with natural order descending" do
|
180
|
+
subject[:sort] = {'$natural' => -1}
|
181
|
+
subject[:sort].should =={'$natural' => -1}
|
182
|
+
end
|
183
|
+
|
184
|
+
should "convert single ascending field (string)" do
|
185
|
+
subject[:sort] = 'foo asc'
|
186
|
+
subject[:sort].should == [['foo', 1]]
|
187
|
+
|
188
|
+
subject[:sort] = 'foo ASC'
|
189
|
+
subject[:sort].should == [['foo', 1]]
|
190
|
+
end
|
191
|
+
|
192
|
+
should "convert single descending field (string)" do
|
193
|
+
subject[:sort] = 'foo desc'
|
194
|
+
subject[:sort].should == [['foo', -1]]
|
195
|
+
|
196
|
+
subject[:sort] = 'foo DESC'
|
197
|
+
subject[:sort].should == [['foo', -1]]
|
198
|
+
end
|
199
|
+
|
200
|
+
should "convert multiple fields (string)" do
|
201
|
+
subject[:sort] = 'foo desc, bar asc'
|
202
|
+
subject[:sort].should == [['foo', -1], ['bar', 1]]
|
203
|
+
end
|
204
|
+
|
205
|
+
should "convert multiple fields and default no direction to ascending (string)" do
|
206
|
+
subject[:sort] = 'foo desc, bar, baz'
|
207
|
+
subject[:sort].should == [['foo', -1], ['bar', 1], ['baz', 1]]
|
208
|
+
end
|
209
|
+
|
210
|
+
should "convert symbol" do
|
211
|
+
subject[:sort] = :name
|
212
|
+
subject[:sort] = [['name', 1]]
|
213
|
+
end
|
214
|
+
|
215
|
+
should "convert operator" do
|
216
|
+
subject[:sort] = :foo.desc
|
217
|
+
subject[:sort].should == [['foo', -1]]
|
218
|
+
end
|
219
|
+
|
220
|
+
should "convert array of operators" do
|
221
|
+
subject[:sort] = [:foo.desc, :bar.asc]
|
222
|
+
subject[:sort].should == [['foo', -1], ['bar', 1]]
|
223
|
+
end
|
224
|
+
|
225
|
+
should "convert array of symbols" do
|
226
|
+
subject[:sort] = [:first_name, :last_name]
|
227
|
+
subject[:sort] = [['first_name', 1], ['last_name', 1]]
|
228
|
+
end
|
229
|
+
|
230
|
+
should "work with array and one string element" do
|
231
|
+
subject[:sort] = ['foo, bar desc']
|
232
|
+
subject[:sort].should == [['foo', 1], ['bar', -1]]
|
233
|
+
end
|
234
|
+
|
235
|
+
should "work with array of single array" do
|
236
|
+
subject[:sort] = [['foo', -1]]
|
237
|
+
subject[:sort].should == [['foo', -1]]
|
238
|
+
end
|
239
|
+
|
240
|
+
should "work with array of multiple arrays" do
|
241
|
+
subject[:sort] = [['foo', -1], ['bar', 1]]
|
242
|
+
subject[:sort].should == [['foo', -1], ['bar', 1]]
|
243
|
+
end
|
244
|
+
|
245
|
+
should "compact nil values in array" do
|
246
|
+
subject[:sort] = [nil, :foo.desc]
|
247
|
+
subject[:sort].should == [['foo', -1]]
|
248
|
+
end
|
249
|
+
|
250
|
+
should "convert array with mix of values" do
|
251
|
+
subject[:sort] = [:foo.desc, 'bar']
|
252
|
+
subject[:sort].should == [['foo', -1], ['bar', 1]]
|
253
|
+
end
|
254
|
+
|
255
|
+
should "convert id to _id" do
|
256
|
+
subject[:sort] = [:id.asc]
|
257
|
+
subject[:sort].should == [['_id', 1]]
|
258
|
+
end
|
259
|
+
|
260
|
+
should "convert string with $natural correctly" do
|
261
|
+
subject[:sort] = '$natural desc'
|
262
|
+
subject[:sort].should == [['$natural', -1]]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
context "#merge" do
|
269
|
+
setup do
|
270
|
+
@o1 = OptionsHash.new(:skip => 5, :sort => :name)
|
271
|
+
@o2 = OptionsHash.new(:limit => 10, :skip => 15)
|
272
|
+
@merged = @o1.merge(@o2)
|
273
|
+
end
|
274
|
+
|
275
|
+
should "override options in first with options in second" do
|
276
|
+
@merged.should == OptionsHash.new(:limit => 10, :skip => 15, :sort => :name)
|
277
|
+
end
|
278
|
+
|
279
|
+
should "return new instance and not change either of the merged" do
|
280
|
+
@o1[:skip].should == 5
|
281
|
+
@o2[:sort].should be_nil
|
282
|
+
@merged.should_not equal(@o1)
|
283
|
+
@merged.should_not equal(@o2)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context "#merge!" do
|
288
|
+
setup do
|
289
|
+
@o1 = OptionsHash.new(:skip => 5, :sort => :name)
|
290
|
+
@o2 = OptionsHash.new(:limit => 10, :skip => 15)
|
291
|
+
@merged = @o1.merge!(@o2)
|
292
|
+
end
|
293
|
+
|
294
|
+
should "override options in first with options in second" do
|
295
|
+
@merged.should == OptionsHash.new(:limit => 10, :skip => 15, :sort => :name)
|
296
|
+
end
|
297
|
+
|
298
|
+
should "just update the first" do
|
299
|
+
@merged.should equal(@o1)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,819 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class QueryTest < Test::Unit::TestCase
|
4
|
+
context "Query" do
|
5
|
+
include Plucky
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@chris = oh(['_id', 'chris'], ['age', 26], ['name', 'Chris'])
|
9
|
+
@steve = oh(['_id', 'steve'], ['age', 29], ['name', 'Steve'])
|
10
|
+
@john = oh(['_id', 'john'], ['age', 28], ['name', 'John'])
|
11
|
+
@collection = DB['users']
|
12
|
+
@collection.insert(@chris)
|
13
|
+
@collection.insert(@steve)
|
14
|
+
@collection.insert(@john)
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#initialize" do
|
18
|
+
setup { @query = Query.new(@collection) }
|
19
|
+
subject { @query }
|
20
|
+
|
21
|
+
should "default options to options hash" do
|
22
|
+
@query.options.should be_instance_of(OptionsHash)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "default criteria to criteria hash" do
|
26
|
+
@query.criteria.should be_instance_of(CriteriaHash)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#initialize_copy" do
|
31
|
+
setup do
|
32
|
+
@original = Query.new(@collection)
|
33
|
+
@cloned = @original.clone
|
34
|
+
end
|
35
|
+
|
36
|
+
should "duplicate options" do
|
37
|
+
@cloned.options.should_not equal(@original.options)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "duplicate criteria" do
|
41
|
+
@cloned.criteria.should_not equal(@original.criteria)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "#[]=" do
|
46
|
+
setup { @query = Query.new(@collection) }
|
47
|
+
subject { @query }
|
48
|
+
|
49
|
+
should "set key on options for option" do
|
50
|
+
subject[:skip] = 1
|
51
|
+
subject[:skip].should == 1
|
52
|
+
end
|
53
|
+
|
54
|
+
should "set key on criteria for criteria" do
|
55
|
+
subject[:foo] = 'bar'
|
56
|
+
subject[:foo].should == 'bar'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#find_each" do
|
61
|
+
should "return a cursor" do
|
62
|
+
cursor = Query.new(@collection).find_each
|
63
|
+
cursor.should be_instance_of(Mongo::Cursor)
|
64
|
+
end
|
65
|
+
|
66
|
+
should "work with and normalize criteria" do
|
67
|
+
cursor = Query.new(@collection).find_each(:id.in => ['john'])
|
68
|
+
cursor.to_a.should == [@john]
|
69
|
+
end
|
70
|
+
|
71
|
+
should "work with and normalize options" do
|
72
|
+
cursor = Query.new(@collection).find_each(:order => :name.asc)
|
73
|
+
cursor.to_a.should == [@chris, @john, @steve]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "#find_one" do
|
78
|
+
should "work with and normalize criteria" do
|
79
|
+
Query.new(@collection).find_one(:id.in => ['john']).should == @john
|
80
|
+
end
|
81
|
+
|
82
|
+
should "work with and normalize options" do
|
83
|
+
Query.new(@collection).find_one(:order => :age.desc).should == @steve
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "#find" do
|
88
|
+
setup do
|
89
|
+
@query = Query.new(@collection)
|
90
|
+
end
|
91
|
+
subject { @query }
|
92
|
+
|
93
|
+
should "work with single id" do
|
94
|
+
@query.find('chris').should == @chris
|
95
|
+
end
|
96
|
+
|
97
|
+
should "work with multiple ids" do
|
98
|
+
@query.find('chris', 'john').should == [@chris, @john]
|
99
|
+
end
|
100
|
+
|
101
|
+
should "work with array of one id" do
|
102
|
+
@query.find(['chris']).should == [@chris]
|
103
|
+
end
|
104
|
+
|
105
|
+
should "work with array of ids" do
|
106
|
+
@query.find(['chris', 'john']).should == [@chris, @john]
|
107
|
+
end
|
108
|
+
|
109
|
+
should "ignore those not found" do
|
110
|
+
@query.find('john', 'frank').should == [@john]
|
111
|
+
end
|
112
|
+
|
113
|
+
should "return nil for nil" do
|
114
|
+
@query.find(nil).should be_nil
|
115
|
+
end
|
116
|
+
|
117
|
+
should "return nil for *nil" do
|
118
|
+
@query.find(*nil).should be_nil
|
119
|
+
end
|
120
|
+
|
121
|
+
should "normalize if using object id" do
|
122
|
+
id = @collection.insert(:name => 'Frank')
|
123
|
+
@query.object_ids([:_id])
|
124
|
+
doc = @query.find(id.to_s)
|
125
|
+
doc['name'].should == 'Frank'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "#per_page" do
|
130
|
+
should "default to 25" do
|
131
|
+
Query.new(@collection).per_page.should == 25
|
132
|
+
end
|
133
|
+
|
134
|
+
should "be changeable and chainable" do
|
135
|
+
query = Query.new(@collection)
|
136
|
+
query.per_page(10).per_page.should == 10
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "#paginate" do
|
141
|
+
setup do
|
142
|
+
@query = Query.new(@collection).sort(:age).per_page(1)
|
143
|
+
end
|
144
|
+
subject { @query }
|
145
|
+
|
146
|
+
should "default to page 1" do
|
147
|
+
subject.paginate.should == [@chris]
|
148
|
+
end
|
149
|
+
|
150
|
+
should "work with other pages" do
|
151
|
+
subject.paginate(:page => 2).should == [@john]
|
152
|
+
subject.paginate(:page => 3).should == [@steve]
|
153
|
+
end
|
154
|
+
|
155
|
+
should "work with string page number" do
|
156
|
+
subject.paginate(:page => '2').should == [@john]
|
157
|
+
end
|
158
|
+
|
159
|
+
should "allow changing per_page" do
|
160
|
+
subject.paginate(:per_page => 2).should == [@chris, @john]
|
161
|
+
end
|
162
|
+
|
163
|
+
should "decorate return value" do
|
164
|
+
docs = subject.paginate
|
165
|
+
docs.should respond_to(:paginator)
|
166
|
+
docs.should respond_to(:total_entries)
|
167
|
+
end
|
168
|
+
|
169
|
+
should "not modify the original query" do
|
170
|
+
subject.paginate(:name => 'John')
|
171
|
+
subject[:page].should be_nil
|
172
|
+
subject[:per_page].should be_nil
|
173
|
+
subject[:name].should be_nil
|
174
|
+
end
|
175
|
+
|
176
|
+
context "with options" do
|
177
|
+
setup do
|
178
|
+
@result = @query.sort(:age).paginate(:age.gt => 27, :per_page => 10)
|
179
|
+
end
|
180
|
+
subject { @result }
|
181
|
+
|
182
|
+
should "only return matching" do
|
183
|
+
subject.should == [@john, @steve]
|
184
|
+
end
|
185
|
+
|
186
|
+
should "correctly count matching" do
|
187
|
+
subject.total_entries.should == 2
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "#all" do
|
193
|
+
should "work with no arguments" do
|
194
|
+
docs = Query.new(@collection).all
|
195
|
+
docs.size.should == 3
|
196
|
+
docs.should include(@john)
|
197
|
+
docs.should include(@steve)
|
198
|
+
docs.should include(@chris)
|
199
|
+
end
|
200
|
+
|
201
|
+
should "work with and normalize criteria" do
|
202
|
+
docs = Query.new(@collection).all(:id.in => ['steve'])
|
203
|
+
docs.should == [@steve]
|
204
|
+
end
|
205
|
+
|
206
|
+
should "work with and normalize options" do
|
207
|
+
docs = Query.new(@collection).all(:order => :name.asc)
|
208
|
+
docs.should == [@chris, @john, @steve]
|
209
|
+
end
|
210
|
+
|
211
|
+
should "not modify original query object" do
|
212
|
+
query = Query.new(@collection)
|
213
|
+
query.all(:name => 'Steve')
|
214
|
+
query[:name].should be_nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "#first" do
|
219
|
+
should "work with and normalize criteria" do
|
220
|
+
Query.new(@collection).first(:age.lt => 29).should == @chris
|
221
|
+
end
|
222
|
+
|
223
|
+
should "work with and normalize options" do
|
224
|
+
Query.new(@collection).first(:age.lte => 29, :order => :name.desc).should == @steve
|
225
|
+
end
|
226
|
+
|
227
|
+
should "not modify original query object" do
|
228
|
+
query = Query.new(@collection)
|
229
|
+
query.first(:name => 'Steve')
|
230
|
+
query[:name].should be_nil
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "#last" do
|
235
|
+
should "work with and normalize criteria" do
|
236
|
+
Query.new(@collection).last(:age.lte => 29, :order => :name.asc).should == @steve
|
237
|
+
end
|
238
|
+
|
239
|
+
should "work with and normalize options" do
|
240
|
+
Query.new(@collection).last(:age.lte => 26, :order => :name.desc).should == @chris
|
241
|
+
end
|
242
|
+
|
243
|
+
should "not modify original query object" do
|
244
|
+
query = Query.new(@collection)
|
245
|
+
query.last(:name => 'Steve')
|
246
|
+
query[:name].should be_nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "#count" do
|
251
|
+
should "work with no arguments" do
|
252
|
+
Query.new(@collection).count.should == 3
|
253
|
+
end
|
254
|
+
|
255
|
+
should "work with and normalize criteria" do
|
256
|
+
Query.new(@collection).count(:age.lte => 28).should == 2
|
257
|
+
end
|
258
|
+
|
259
|
+
should "not modify original query object" do
|
260
|
+
query = Query.new(@collection)
|
261
|
+
query.count(:name => 'Steve')
|
262
|
+
query[:name].should be_nil
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "#size" do
|
267
|
+
should "work just like count without options" do
|
268
|
+
Query.new(@collection).size.should == 3
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "#distinct" do
|
273
|
+
setup do
|
274
|
+
# same age as John
|
275
|
+
@mark = oh(['_id', 'mark'], ['age', 28], ['name', 'Mark'])
|
276
|
+
@collection.insert(@mark)
|
277
|
+
end
|
278
|
+
|
279
|
+
should "work with just a key" do
|
280
|
+
Query.new(@collection).distinct(:age).sort.should == [26, 28, 29]
|
281
|
+
end
|
282
|
+
|
283
|
+
should "work with criteria" do
|
284
|
+
Query.new(@collection).distinct(:age, :age.gt => 26).sort.should == [28, 29]
|
285
|
+
end
|
286
|
+
|
287
|
+
should "not modify the original query object" do
|
288
|
+
query = Query.new(@collection)
|
289
|
+
query.distinct(:age, :name => 'Mark').should == [28]
|
290
|
+
query[:name].should be_nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "#remove" do
|
295
|
+
should "work with no arguments" do
|
296
|
+
lambda { Query.new(@collection).remove }.should change { @collection.count }.by(3)
|
297
|
+
end
|
298
|
+
|
299
|
+
should "work with and normalize criteria" do
|
300
|
+
lambda { Query.new(@collection).remove(:age.lte => 28) }.should change { @collection.count }
|
301
|
+
end
|
302
|
+
|
303
|
+
should "work with options" do
|
304
|
+
lambda { Query.new(@collection).remove({:age.lte => 28}, :safe => true) }.should change { @collection.count }
|
305
|
+
end
|
306
|
+
|
307
|
+
should "not modify original query object" do
|
308
|
+
query = Query.new(@collection)
|
309
|
+
query.remove(:name => 'Steve')
|
310
|
+
query[:name].should be_nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context "#update" do
|
315
|
+
setup do
|
316
|
+
@query = Query.new(@collection).where('_id' => 'john')
|
317
|
+
end
|
318
|
+
|
319
|
+
should "work with document" do
|
320
|
+
@query.update('$set' => {'age' => 29})
|
321
|
+
doc = @query.first('_id' => 'john')
|
322
|
+
doc['age'].should be(29)
|
323
|
+
end
|
324
|
+
|
325
|
+
should "work with document and driver options" do
|
326
|
+
@query.update({'$set' => {'age' => 30}}, :multi => true)
|
327
|
+
@query.each do |doc|
|
328
|
+
doc['age'].should be(30)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context "#[]" do
|
334
|
+
should "return value if key in criteria (symbol)" do
|
335
|
+
Query.new(@collection, :count => 1)[:count].should == 1
|
336
|
+
end
|
337
|
+
|
338
|
+
should "return value if key in criteria (string)" do
|
339
|
+
Query.new(@collection, :count => 1)['count'].should == 1
|
340
|
+
end
|
341
|
+
|
342
|
+
should "return nil if key not in criteria" do
|
343
|
+
Query.new(@collection)[:count].should be_nil
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
context "#[]=" do
|
348
|
+
setup { @query = Query.new(@collection) }
|
349
|
+
|
350
|
+
should "set the value of the given criteria key" do
|
351
|
+
@query[:count] = 1
|
352
|
+
@query[:count].should == 1
|
353
|
+
end
|
354
|
+
|
355
|
+
should "overwrite value if key already exists" do
|
356
|
+
@query[:count] = 1
|
357
|
+
@query[:count] = 2
|
358
|
+
@query[:count].should == 2
|
359
|
+
end
|
360
|
+
|
361
|
+
should "normalize value" do
|
362
|
+
now = Time.now
|
363
|
+
@query[:published_at] = now
|
364
|
+
@query[:published_at].should == now.utc
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
context "#fields" do
|
369
|
+
setup { @query = Query.new(@collection) }
|
370
|
+
subject { @query }
|
371
|
+
|
372
|
+
should "work" do
|
373
|
+
subject.fields(:name).first(:id => 'john').keys.should == ['_id', 'name']
|
374
|
+
end
|
375
|
+
|
376
|
+
should "return new instance of query" do
|
377
|
+
new_query = subject.fields(:name)
|
378
|
+
new_query.should_not equal(subject)
|
379
|
+
subject[:fields].should be_nil
|
380
|
+
end
|
381
|
+
|
382
|
+
should "work with hash" do
|
383
|
+
subject.fields(:name => 0).
|
384
|
+
first(:id => 'john').keys.sort.
|
385
|
+
should == ['_id', 'age']
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
context "#ignore" do
|
390
|
+
setup {@query = Query.new(@collection)}
|
391
|
+
subject {@query}
|
392
|
+
|
393
|
+
should "include a list of keys to ignore" do
|
394
|
+
new_query = subject.ignore(:name).first(:id => 'john')
|
395
|
+
new_query.keys.should == ['_id', 'age']
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
context "#only" do
|
400
|
+
setup {@query = Query.new(@collection)}
|
401
|
+
subject {@query}
|
402
|
+
|
403
|
+
should "inclue a list of keys with others excluded" do
|
404
|
+
new_query = subject.only(:name).first(:id => 'john')
|
405
|
+
new_query.keys.should == ['_id', 'name']
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
409
|
+
|
410
|
+
context "#skip" do
|
411
|
+
setup { @query = Query.new(@collection) }
|
412
|
+
subject { @query }
|
413
|
+
|
414
|
+
should "work" do
|
415
|
+
subject.skip(2).all(:order => :age).should == [@steve]
|
416
|
+
end
|
417
|
+
|
418
|
+
should "set skip option" do
|
419
|
+
subject.skip(5).options[:skip].should == 5
|
420
|
+
end
|
421
|
+
|
422
|
+
should "override existing skip" do
|
423
|
+
subject.skip(5).skip(10).options[:skip].should == 10
|
424
|
+
end
|
425
|
+
|
426
|
+
should "return nil for nil" do
|
427
|
+
subject.skip.options[:skip].should be_nil
|
428
|
+
end
|
429
|
+
|
430
|
+
should "return new instance of query" do
|
431
|
+
new_query = subject.skip(2)
|
432
|
+
new_query.should_not equal(subject)
|
433
|
+
subject[:skip].should be_nil
|
434
|
+
end
|
435
|
+
|
436
|
+
should "alias to offset" do
|
437
|
+
subject.offset(5).options[:skip].should == 5
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
context "#limit" do
|
442
|
+
setup { @query = Query.new(@collection) }
|
443
|
+
subject { @query }
|
444
|
+
|
445
|
+
should "work" do
|
446
|
+
subject.limit(2).all(:order => :age).should == [@chris, @john]
|
447
|
+
end
|
448
|
+
|
449
|
+
should "set limit option" do
|
450
|
+
subject.limit(5).options[:limit].should == 5
|
451
|
+
end
|
452
|
+
|
453
|
+
should "overwrite existing limit" do
|
454
|
+
subject.limit(5).limit(15).options[:limit].should == 15
|
455
|
+
end
|
456
|
+
|
457
|
+
should "return new instance of query" do
|
458
|
+
new_query = subject.limit(2)
|
459
|
+
new_query.should_not equal(subject)
|
460
|
+
subject[:limit].should be_nil
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
context "#sort" do
|
465
|
+
setup { @query = Query.new(@collection) }
|
466
|
+
subject { @query }
|
467
|
+
|
468
|
+
should "work" do
|
469
|
+
subject.sort(:age).all.should == [@chris, @john, @steve]
|
470
|
+
subject.sort(:age.desc).all.should == [@steve, @john, @chris]
|
471
|
+
end
|
472
|
+
|
473
|
+
should "work with symbol operators" do
|
474
|
+
subject.sort(:foo.asc, :bar.desc).options[:sort].should == [['foo', 1], ['bar', -1]]
|
475
|
+
end
|
476
|
+
|
477
|
+
should "work with string" do
|
478
|
+
subject.sort('foo, bar desc').options[:sort].should == [['foo', 1], ['bar', -1]]
|
479
|
+
end
|
480
|
+
|
481
|
+
should "work with just a symbol" do
|
482
|
+
subject.sort(:foo).options[:sort].should == [['foo', 1]]
|
483
|
+
end
|
484
|
+
|
485
|
+
should "work with symbol descending" do
|
486
|
+
subject.sort(:foo.desc).options[:sort].should == [['foo', -1]]
|
487
|
+
end
|
488
|
+
|
489
|
+
should "work with multiple symbols" do
|
490
|
+
subject.sort(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
|
491
|
+
end
|
492
|
+
|
493
|
+
should "return new instance of query" do
|
494
|
+
new_query = subject.sort(:name)
|
495
|
+
new_query.should_not equal(subject)
|
496
|
+
subject[:sort].should be_nil
|
497
|
+
end
|
498
|
+
|
499
|
+
should "be aliased to order" do
|
500
|
+
subject.order(:foo).options[:sort].should == [['foo', 1]]
|
501
|
+
subject.order(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
context "#reverse" do
|
506
|
+
setup { @query = Query.new(@collection) }
|
507
|
+
subject { @query }
|
508
|
+
|
509
|
+
should "work" do
|
510
|
+
subject.sort(:age).reverse.all.should == [@steve, @john, @chris]
|
511
|
+
end
|
512
|
+
|
513
|
+
should "not error if no sort provided" do
|
514
|
+
assert_nothing_raised do
|
515
|
+
subject.reverse
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
should "reverse the sort order" do
|
520
|
+
subject.sort('foo asc, bar desc').
|
521
|
+
reverse.options[:sort].should == [['foo', -1], ['bar', 1]]
|
522
|
+
end
|
523
|
+
|
524
|
+
should "return new instance of query" do
|
525
|
+
sorted_query = subject.sort(:name)
|
526
|
+
new_query = sorted_query.reverse
|
527
|
+
new_query.should_not equal(sorted_query)
|
528
|
+
sorted_query[:sort].should == [['name', 1]]
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
context "#amend" do
|
533
|
+
should "normalize and update options" do
|
534
|
+
Query.new(@collection).amend(:order => :age.desc).options[:sort].should == [['age', -1]]
|
535
|
+
end
|
536
|
+
|
537
|
+
should "work with simple stuff" do
|
538
|
+
Query.new(@collection).
|
539
|
+
amend(:foo => 'bar').
|
540
|
+
amend(:baz => 'wick').
|
541
|
+
criteria.should == CriteriaHash.new(:foo => 'bar', :baz => 'wick')
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
context "#where" do
|
546
|
+
setup { @query = Query.new(@collection) }
|
547
|
+
subject { @query }
|
548
|
+
|
549
|
+
should "work" do
|
550
|
+
subject.where(:age.lt => 29).where(:name => 'Chris').all.should == [@chris]
|
551
|
+
end
|
552
|
+
|
553
|
+
should "work with literal regexp" do
|
554
|
+
subject.where(:name => /^c/i).all.should == [@chris]
|
555
|
+
end
|
556
|
+
|
557
|
+
should "update criteria" do
|
558
|
+
subject.
|
559
|
+
where(:moo => 'cow').
|
560
|
+
where(:foo => 'bar').
|
561
|
+
criteria.should == CriteriaHash.new(:foo => 'bar', :moo => 'cow')
|
562
|
+
end
|
563
|
+
|
564
|
+
should "get normalized" do
|
565
|
+
subject.
|
566
|
+
where(:moo => 'cow').
|
567
|
+
where(:foo.in => ['bar']).
|
568
|
+
criteria.should == CriteriaHash.new(:moo => 'cow', :foo => {'$in' => ['bar']})
|
569
|
+
end
|
570
|
+
|
571
|
+
should "normalize merged criteria" do
|
572
|
+
subject.
|
573
|
+
where(:foo => 'bar').
|
574
|
+
where(:foo => 'baz').
|
575
|
+
criteria.should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
|
576
|
+
end
|
577
|
+
|
578
|
+
should "return new instance of query" do
|
579
|
+
new_query = subject.where(:name => 'John')
|
580
|
+
new_query.should_not equal(subject)
|
581
|
+
subject[:name].should be_nil
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
context "#filter" do
|
586
|
+
setup { @query = Query.new(@collection) }
|
587
|
+
subject { @query }
|
588
|
+
|
589
|
+
should "work the same as where" do
|
590
|
+
subject.filter(:age.lt => 29).filter(:name => 'Chris').all.should == [@chris]
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
context "#empty?" do
|
595
|
+
should "be true if empty" do
|
596
|
+
@collection.remove
|
597
|
+
Query.new(@collection).should be_empty
|
598
|
+
end
|
599
|
+
|
600
|
+
should "be false if not empty" do
|
601
|
+
Query.new(@collection).should_not be_empty
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
context "#exists?" do
|
606
|
+
should "be true if found" do
|
607
|
+
Query.new(@collection).exists?(:name => 'John').should be(true)
|
608
|
+
end
|
609
|
+
|
610
|
+
should "be false if not found" do
|
611
|
+
Query.new(@collection).exists?(:name => 'Billy Bob').should be(false)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
context "#exist?" do
|
616
|
+
should "be true if found" do
|
617
|
+
Query.new(@collection).exist?(:name => 'John').should be(true)
|
618
|
+
end
|
619
|
+
|
620
|
+
should "be false if not found" do
|
621
|
+
Query.new(@collection).exist?(:name => 'Billy Bob').should be(false)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
context "#include?" do
|
626
|
+
should "be true if included" do
|
627
|
+
Query.new(@collection).include?(@john).should be(true)
|
628
|
+
end
|
629
|
+
|
630
|
+
should "be false if not included" do
|
631
|
+
Query.new(@collection).include?(['_id', 'frankyboy']).should be(false)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
context "#to_a" do
|
636
|
+
should "return all documents the query matches" do
|
637
|
+
Query.new(@collection).sort(:name).to_a.
|
638
|
+
should == [@chris, @john, @steve]
|
639
|
+
|
640
|
+
Query.new(@collection).where(:name => 'John').sort(:name).to_a.
|
641
|
+
should == [@john]
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
context "#each" do
|
646
|
+
should "iterate through matching documents" do
|
647
|
+
docs = []
|
648
|
+
Query.new(@collection).sort(:name).each do |doc|
|
649
|
+
docs << doc
|
650
|
+
end
|
651
|
+
docs.should == [@chris, @john, @steve]
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
context "enumerables" do
|
656
|
+
should "work" do
|
657
|
+
query = Query.new(@collection).sort(:name)
|
658
|
+
query.map { |doc| doc['name'] }.should == %w(Chris John Steve)
|
659
|
+
query.collect { |doc| doc['name'] }.should == %w(Chris John Steve)
|
660
|
+
query.detect { |doc| doc['name'] == 'John' }.should == @john
|
661
|
+
query.min { |a, b| a['age'] <=> b['age'] }.should == @chris
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
context "#object_ids" do
|
666
|
+
setup { @query = Query.new(@collection) }
|
667
|
+
subject { @query }
|
668
|
+
|
669
|
+
should "set criteria's object_ids" do
|
670
|
+
subject.criteria.expects(:object_ids=).with([:foo, :bar])
|
671
|
+
subject.object_ids(:foo, :bar)
|
672
|
+
end
|
673
|
+
|
674
|
+
should "return current object ids if keys argument is empty" do
|
675
|
+
subject.object_ids(:foo, :bar)
|
676
|
+
subject.object_ids.should == [:foo, :bar]
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
context "#merge" do
|
681
|
+
should "overwrite options" do
|
682
|
+
query1 = Query.new(@collection, :skip => 5, :limit => 5)
|
683
|
+
query2 = Query.new(@collection, :skip => 10, :limit => 10)
|
684
|
+
new_query = query1.merge(query2)
|
685
|
+
new_query.options[:skip].should == 10
|
686
|
+
new_query.options[:limit].should == 10
|
687
|
+
end
|
688
|
+
|
689
|
+
should "merge criteria" do
|
690
|
+
query1 = Query.new(@collection, :foo => 'bar')
|
691
|
+
query2 = Query.new(@collection, :foo => 'baz', :fent => 'wick')
|
692
|
+
new_query = query1.merge(query2)
|
693
|
+
new_query.criteria[:fent].should == 'wick'
|
694
|
+
new_query.criteria[:foo].should == {'$in' => %w[bar baz]}
|
695
|
+
end
|
696
|
+
|
697
|
+
should "not affect either of the merged queries" do
|
698
|
+
query1 = Query.new(@collection, :foo => 'bar', :limit => 5)
|
699
|
+
query2 = Query.new(@collection, :foo => 'baz', :limit => 10)
|
700
|
+
new_query = query1.merge(query2)
|
701
|
+
query1[:foo].should == 'bar'
|
702
|
+
query1[:limit].should == 5
|
703
|
+
query2[:foo].should == 'baz'
|
704
|
+
query2[:limit].should == 10
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
context "Criteria/option auto-detection" do
|
709
|
+
should "know :conditions are criteria" do
|
710
|
+
query = Query.new(@collection, :conditions => {:foo => 'bar'})
|
711
|
+
query.criteria.should == CriteriaHash.new(:foo => 'bar')
|
712
|
+
query.options.keys.should_not include(:conditions)
|
713
|
+
end
|
714
|
+
|
715
|
+
{
|
716
|
+
:fields => ['foo'],
|
717
|
+
:sort => [['foo', 1]],
|
718
|
+
:hint => '',
|
719
|
+
:skip => 0,
|
720
|
+
:limit => 0,
|
721
|
+
:batch_size => 0,
|
722
|
+
:timeout => 0,
|
723
|
+
}.each do |option, value|
|
724
|
+
should "know #{option} is an option" do
|
725
|
+
query = Query.new(@collection, option => value)
|
726
|
+
query.options[option].should == value
|
727
|
+
query.criteria.keys.should_not include(option)
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
should "know select is an option and remove it from options" do
|
732
|
+
query = Query.new(@collection, :select => 'foo')
|
733
|
+
query.options[:fields].should == ['foo']
|
734
|
+
query.criteria.keys.should_not include(:select)
|
735
|
+
query.options.keys.should_not include(:select)
|
736
|
+
end
|
737
|
+
|
738
|
+
should "know order is an option and remove it from options" do
|
739
|
+
query = Query.new(@collection, :order => 'foo')
|
740
|
+
query.options[:sort].should == [['foo', 1]]
|
741
|
+
query.criteria.keys.should_not include(:order)
|
742
|
+
query.options.keys.should_not include(:order)
|
743
|
+
end
|
744
|
+
|
745
|
+
should "know offset is an option and remove it from options" do
|
746
|
+
query = Query.new(@collection, :offset => 0)
|
747
|
+
query.options[:skip].should == 0
|
748
|
+
query.criteria.keys.should_not include(:offset)
|
749
|
+
query.options.keys.should_not include(:offset)
|
750
|
+
end
|
751
|
+
|
752
|
+
should "work with full range of things" do
|
753
|
+
query = Query.new(@collection, {
|
754
|
+
:foo => 'bar',
|
755
|
+
:baz => true,
|
756
|
+
:sort => [['foo', 1]],
|
757
|
+
:fields => ['foo', 'baz'],
|
758
|
+
:limit => 10,
|
759
|
+
:skip => 10,
|
760
|
+
})
|
761
|
+
query.criteria.should == CriteriaHash.new(:foo => 'bar', :baz => true)
|
762
|
+
query.options.should == OptionsHash.new({
|
763
|
+
:sort => [['foo', 1]],
|
764
|
+
:fields => ['foo', 'baz'],
|
765
|
+
:limit => 10,
|
766
|
+
:skip => 10,
|
767
|
+
})
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
should "inspect pretty" do
|
772
|
+
inspect = Query.new(@collection, :baz => 'wick', :foo => 'bar').inspect
|
773
|
+
inspect.should == '#<Plucky::Query baz: "wick", foo: "bar">'
|
774
|
+
end
|
775
|
+
|
776
|
+
should "delegate simple? to criteria" do
|
777
|
+
query = Query.new(@collection)
|
778
|
+
query.criteria.expects(:simple?)
|
779
|
+
query.simple?
|
780
|
+
end
|
781
|
+
|
782
|
+
should "delegate fields? to options" do
|
783
|
+
query = Query.new(@collection)
|
784
|
+
query.options.expects(:fields?)
|
785
|
+
query.fields?
|
786
|
+
end
|
787
|
+
|
788
|
+
context "#explain" do
|
789
|
+
setup { @query = Query.new(@collection) }
|
790
|
+
subject { @query }
|
791
|
+
|
792
|
+
should "work" do
|
793
|
+
explain = subject.where(:age.lt => 28).explain
|
794
|
+
explain['cursor'].should == 'BasicCursor'
|
795
|
+
explain['nscanned'].should == 3
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
context "Transforming documents" do
|
800
|
+
setup do
|
801
|
+
transformer = lambda { |doc| @user_class.new(doc['_id'], doc['name'], doc['age']) }
|
802
|
+
@user_class = Struct.new(:id, :name, :age)
|
803
|
+
@query = Query.new(@collection, :transformer => transformer)
|
804
|
+
end
|
805
|
+
|
806
|
+
should "work with find_one" do
|
807
|
+
result = @query.find_one('_id' => 'john')
|
808
|
+
result.should be_instance_of(@user_class)
|
809
|
+
end
|
810
|
+
|
811
|
+
should "work with find_each" do
|
812
|
+
results = @query.find_each
|
813
|
+
results.each do |result|
|
814
|
+
result.should be_instance_of(@user_class)
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|