plucky 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/UPGRADES ADDED
@@ -0,0 +1,2 @@
1
+ Apr 23, 2011 0.3.8 => 0.4
2
+ * Query#update was renamed to #amend.
data/lib/plucky.rb CHANGED
@@ -14,17 +14,25 @@ module Plucky
14
14
  autoload :Paginator, 'plucky/pagination/paginator'
15
15
  end
16
16
 
17
+ # Array of methods that actually perform queries
18
+ Methods = [
19
+ :where, :filter, :limit, :skip, :offset, :sort, :order,
20
+ :fields, :ignore, :only,
21
+ :each, :find_each,
22
+ :count, :size, :distinct,
23
+ :last, :first, :all, :paginate,
24
+ :exists?, :exist?, :empty?,
25
+ :to_a, :remove,
26
+ ]
27
+
17
28
  def self.to_object_id(value)
18
- if value.nil? || (value.respond_to?(:empty?) && value.empty?)
19
- nil
20
- elsif value.is_a?(BSON::ObjectId)
21
- value
29
+ return value if value.is_a?(BSON::ObjectId)
30
+ return nil if value.nil? || (value.respond_to?(:empty?) && value.empty?)
31
+
32
+ if BSON::ObjectId.legal?(value.to_s)
33
+ BSON::ObjectId.from_string(value.to_s)
22
34
  else
23
- if BSON::ObjectId.legal?(value.to_s)
24
- BSON::ObjectId.from_string(value.to_s)
25
- else
26
- value
27
- end
35
+ value
28
36
  end
29
37
  end
30
38
  end
@@ -0,0 +1,9 @@
1
+ if defined?(NewRelic)
2
+ Plucky::Query.class_eval do
3
+ include NewRelic::Agent::MethodTracer
4
+
5
+ Plucky::Methods.each do |method_name|
6
+ add_method_tracer(method_name.to_sym)
7
+ end
8
+ end
9
+ end
data/lib/plucky/query.rb CHANGED
@@ -8,7 +8,8 @@ module Plucky
8
8
  OptionKeys = [
9
9
  :select, :offset, :order, # MM
10
10
  :fields, :skip, :limit, :sort, :hint, :snapshot, # Ruby Driver
11
- :batch_size, :timeout, :transformer # Ruby Driver
11
+ :batch_size, :timeout, :transformer, # Ruby Driver
12
+ :max_scan, :show_disk_loc, :return_key, # Ruby Driver
12
13
  ]
13
14
 
14
15
  attr_reader :criteria, :options, :collection
@@ -85,9 +86,14 @@ module Plucky
85
86
  find_each.each { |doc| yield(doc) }
86
87
  end
87
88
 
88
- def remove(opts={})
89
+ def remove(opts={}, driver_opts={})
89
90
  query = clone.amend(opts)
90
- query.collection.remove(query.criteria.to_hash)
91
+ query.collection.remove(query.criteria.to_hash, driver_opts)
92
+ end
93
+
94
+ def update(document, driver_opts={})
95
+ query = clone
96
+ query.collection.update(query.criteria.to_hash, document, driver_opts)
91
97
  end
92
98
 
93
99
  def count(opts={})
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
  module Plucky
3
- Version = '0.4.0'
4
- MongoVersion = '~> 1.3'
3
+ Version = '0.4.1'
4
+ MongoVersion = '~> 1.3.1'
5
5
  end
data/test/helper.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ gem 'jnunemaker-matchy', '~> 0.4.0'
3
+ gem 'log_buddy'
4
+ gem 'shoulda', '~> 2.11'
5
+ gem 'mocha', '~> 0.9.8'
6
+
7
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
+ require 'plucky'
9
+
10
+ require 'fileutils'
11
+ require 'logger'
12
+ require 'pp'
13
+
14
+ require 'log_buddy'
15
+ require 'shoulda'
16
+ require 'matchy'
17
+ require 'mocha'
18
+
19
+ log_dir = File.expand_path('../../log', __FILE__)
20
+ FileUtils.mkdir_p(log_dir)
21
+ Log = Logger.new(File.join(log_dir, 'test.log'))
22
+
23
+ LogBuddy.init :logger => Log
24
+
25
+ connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => Log)
26
+ DB = connection.db('test')
27
+
28
+ class Test::Unit::TestCase
29
+ def setup
30
+ DB.collections.map do |collection|
31
+ collection.remove
32
+ collection.drop_indexes
33
+ end
34
+ end
35
+
36
+ def oh(*args)
37
+ BSON::OrderedHash.new.tap do |hash|
38
+ args.each { |a| hash[a[0]] = a[1] }
39
+ end
40
+ end
41
+ end
42
+
43
+ operators = %w{gt lt gte lte ne in nin mod all size exists}
44
+ operators.delete('size') if RUBY_VERSION >= '1.9.1'
45
+ SymbolOperators = operators
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ class PaginatorTest < Test::Unit::TestCase
4
+ include Plucky::Pagination
5
+
6
+ context "Object decorated with Decorator with paginator set" do
7
+ setup do
8
+ @object = [1, 2, 3, 4]
9
+ @object_id = @object.object_id
10
+ @paginator = Paginator.new(20, 2, 10)
11
+ @object.extend(Decorator)
12
+ @object.paginator(@paginator)
13
+ end
14
+ subject { @object }
15
+
16
+ should "be able to get paginator" do
17
+ subject.paginator.should == @paginator
18
+ end
19
+
20
+ [:total_entries, :current_page, :per_page, :total_pages, :out_of_bounds?,
21
+ :previous_page, :next_page, :skip, :limit, :offset].each do |method|
22
+ should "delegate #{method} to paginator" do
23
+ subject.send(method).should == @paginator.send(method)
24
+ end
25
+ end
26
+
27
+ should "not interfere with other methods on the object" do
28
+ @object.object_id.should == @object_id
29
+ @object.should == [1, 2, 3, 4]
30
+ @object.size.should == 4
31
+ @object.select { |o| o > 2 }.should == [3, 4]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,120 @@
1
+ require 'helper'
2
+
3
+ class PaginatorTest < Test::Unit::TestCase
4
+ include Plucky::Pagination
5
+
6
+ context "#initialize" do
7
+ context "with total and page" do
8
+ setup { @paginator = Paginator.new(20, 2) }
9
+ subject { @paginator }
10
+
11
+ should "set total" do
12
+ subject.total_entries.should == 20
13
+ end
14
+
15
+ should "set page" do
16
+ subject.current_page.should == 2
17
+ end
18
+
19
+ should "default per_page to 25" do
20
+ subject.per_page.should == 25
21
+ end
22
+ end
23
+
24
+ context "with total, page and per_page" do
25
+ setup { @paginator = Paginator.new(20, 2, 10) }
26
+ subject { @paginator }
27
+
28
+ should "set total" do
29
+ subject.total_entries.should == 20
30
+ end
31
+
32
+ should "set page" do
33
+ subject.current_page.should == 2
34
+ end
35
+
36
+ should "set per_page" do
37
+ subject.per_page.should == 10
38
+ end
39
+ end
40
+
41
+ context "with string values for total, page and per_page" do
42
+ setup { @paginator = Paginator.new('20', '2', '10') }
43
+ subject { @paginator }
44
+
45
+ should "set total" do
46
+ subject.total_entries.should == 20
47
+ end
48
+
49
+ should "set page" do
50
+ subject.current_page.should == 2
51
+ end
52
+
53
+ should "set per_page" do
54
+ subject.per_page.should == 10
55
+ end
56
+ end
57
+
58
+ context "with page less than 1" do
59
+ setup { @paginator = Paginator.new(20, -2, 10) }
60
+ subject { @paginator }
61
+
62
+ should "set page to 1" do
63
+ subject.current_page.should == 1
64
+ end
65
+ end
66
+ end
67
+
68
+ should "alias limit to per_page" do
69
+ Paginator.new(30, 2, 30).limit.should == 30
70
+ end
71
+
72
+ should "be know total number of pages" do
73
+ Paginator.new(43, 2, 7).total_pages.should == 7
74
+ Paginator.new(40, 2, 10).total_pages.should == 4
75
+ end
76
+
77
+ context "#out_of_bounds?" do
78
+ should "be true if current_page is greater than total_pages" do
79
+ Paginator.new(2, 3, 1).should be_out_of_bounds
80
+ end
81
+
82
+ should "be false if current page is less than total_pages" do
83
+ Paginator.new(2, 1, 1).should_not be_out_of_bounds
84
+ end
85
+
86
+ should "be false if current page equals total_pages" do
87
+ Paginator.new(2, 2, 1).should_not be_out_of_bounds
88
+ end
89
+ end
90
+
91
+ context "#previous_page" do
92
+ should "be nil if there is no page less than current" do
93
+ Paginator.new(2, 1, 1).previous_page.should be_nil
94
+ end
95
+
96
+ should "be number less than current page if there is one" do
97
+ Paginator.new(2, 2, 1).previous_page.should == 1
98
+ end
99
+ end
100
+
101
+ context "#next_page" do
102
+ should "be nil if no page greater than current page" do
103
+ Paginator.new(2, 2, 1).next_page.should be_nil
104
+ end
105
+
106
+ should "be number greater than current page if there is one" do
107
+ Paginator.new(2, 1, 1).next_page.should == 2
108
+ end
109
+ end
110
+
111
+ context "#skip" do
112
+ should "work" do
113
+ Paginator.new(30, 3, 10).skip.should == 20
114
+ end
115
+
116
+ should "be aliased to offset for will paginate" do
117
+ Paginator.new(30, 3, 10).offset.should == 20
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,296 @@
1
+ require 'helper'
2
+
3
+ class CriteriaHashTest < Test::Unit::TestCase
4
+ include Plucky
5
+
6
+ context "Plucky::CriteriaHash" do
7
+ should "delegate missing methods to the source hash" do
8
+ hash = {:baz => 'wick', :foo => 'bar'}
9
+ criteria = CriteriaHash.new(hash)
10
+ criteria[:foo].should == 'bar'
11
+ criteria[:baz].should == 'wick'
12
+ criteria.keys.to_set.should == [:baz, :foo].to_set
13
+ end
14
+
15
+ SymbolOperators.each do |operator|
16
+ should "work with #{operator} symbol operator" do
17
+ CriteriaHash.new(:age.send(operator) => 21)[:age].should == {"$#{operator}" => 21}
18
+ end
19
+ end
20
+
21
+ should "handle multiple symbol operators on the same field" do
22
+ CriteriaHash.new(:age.gt => 12, :age.lt => 20)[:age].should == {
23
+ '$gt' => 12, '$lt' => 20
24
+ }
25
+ end
26
+
27
+ context "#initialize_copy" do
28
+ setup do
29
+ @original = CriteriaHash.new({
30
+ :comments => {:_id => 1}, :tags => ['mongo', 'ruby'],
31
+ }, :object_ids => [:_id])
32
+ @cloned = @original.clone
33
+ end
34
+
35
+ should "duplicate source hash" do
36
+ @cloned.source.should_not equal(@original.source)
37
+ end
38
+
39
+ should "duplicate options hash" do
40
+ @cloned.options.should_not equal(@original.options)
41
+ end
42
+
43
+ should "clone duplicable? values" do
44
+ @cloned[:comments].should_not equal(@original[:comments])
45
+ @cloned[:tags].should_not equal(@original[:tags])
46
+ end
47
+ end
48
+
49
+ context "#object_ids=" do
50
+ should "work with array" do
51
+ criteria = CriteriaHash.new
52
+ criteria.object_ids = [:_id]
53
+ criteria.object_ids.should == [:_id]
54
+ end
55
+
56
+ should "flatten multi-dimensional array" do
57
+ criteria = CriteriaHash.new
58
+ criteria.object_ids = [[:_id]]
59
+ criteria.object_ids.should == [:_id]
60
+ end
61
+
62
+ should "raise argument error if not array" do
63
+ assert_raises(ArgumentError) { CriteriaHash.new.object_ids = {} }
64
+ assert_raises(ArgumentError) { CriteriaHash.new.object_ids = nil }
65
+ assert_raises(ArgumentError) { CriteriaHash.new.object_ids = 'foo' }
66
+ end
67
+ end
68
+
69
+ context "#[]=" do
70
+ should "leave string values for string keys alone" do
71
+ criteria = CriteriaHash.new
72
+ criteria[:foo] = 'bar'
73
+ criteria[:foo].should == 'bar'
74
+ end
75
+
76
+ should "convert string values to object ids for object id keys" do
77
+ id = BSON::ObjectId.new
78
+ criteria = CriteriaHash.new({}, :object_ids => [:_id])
79
+ criteria[:_id] = id.to_s
80
+ criteria[:_id].should == id
81
+ end
82
+
83
+ should "convert sets to arrays" do
84
+ criteria = CriteriaHash.new
85
+ criteria[:foo] = [1, 2].to_set
86
+ criteria[:foo].should == {'$in' => [1, 2]}
87
+ end
88
+
89
+ should "convert times to utc" do
90
+ time = Time.now
91
+ criteria = CriteriaHash.new
92
+ criteria[:foo] = time
93
+ criteria[:foo].should be_utc
94
+ criteria[:foo].should == time.utc
95
+ end
96
+
97
+ should "convert :id to :_id" do
98
+ criteria = CriteriaHash.new
99
+ criteria[:id] = 1
100
+ criteria[:_id].should == 1
101
+ criteria[:id].should be_nil
102
+ end
103
+
104
+ should "work with symbol operators" do
105
+ criteria = CriteriaHash.new
106
+ criteria[:_id.in] = ['foo']
107
+ criteria[:_id].should == {'$in' => ['foo']}
108
+ end
109
+
110
+ should "set each of the conditions pairs" do
111
+ criteria = CriteriaHash.new
112
+ criteria[:conditions] = {:_id => 'john', :foo => 'bar'}
113
+ criteria[:_id].should == 'john'
114
+ criteria[:foo].should == 'bar'
115
+ end
116
+ end
117
+
118
+ context "with id key" do
119
+ should "convert to _id" do
120
+ id = BSON::ObjectId.new
121
+ criteria = CriteriaHash.new(:id => id)
122
+ criteria[:_id].should == id
123
+ criteria[:id].should be_nil
124
+ end
125
+
126
+ should "convert id with symbol operator to _id with modifier" do
127
+ id = BSON::ObjectId.new
128
+ criteria = CriteriaHash.new(:id.ne => id)
129
+ criteria[:_id].should == {'$ne' => id}
130
+ criteria[:id].should be_nil
131
+ end
132
+ end
133
+
134
+ context "with time value" do
135
+ should "convert to utc if not utc" do
136
+ CriteriaHash.new(:created_at => Time.now)[:created_at].utc?.should be(true)
137
+ end
138
+
139
+ should "leave utc alone" do
140
+ CriteriaHash.new(:created_at => Time.now.utc)[:created_at].utc?.should be(true)
141
+ end
142
+ end
143
+
144
+ context "with array value" do
145
+ should "default to $in" do
146
+ CriteriaHash.new(:numbers => [1,2,3])[:numbers].should == {'$in' => [1,2,3]}
147
+ end
148
+
149
+ should "use existing modifier if present" do
150
+ CriteriaHash.new(:numbers => {'$all' => [1,2,3]})[:numbers].should == {'$all' => [1,2,3]}
151
+ CriteriaHash.new(:numbers => {'$any' => [1,2,3]})[:numbers].should == {'$any' => [1,2,3]}
152
+ end
153
+
154
+ should "not turn value to $in with $or key" do
155
+ CriteriaHash.new(:$or => [{:numbers => 1}, {:numbers => 2}] )[:$or].should == [{:numbers=>1}, {:numbers=>2}]
156
+ end
157
+ end
158
+
159
+ context "with set value" do
160
+ should "default to $in and convert to array" do
161
+ CriteriaHash.new(:numbers => [1,2,3].to_set)[:numbers].should == {'$in' => [1,2,3]}
162
+ end
163
+
164
+ should "use existing modifier if present and convert to array" do
165
+ CriteriaHash.new(:numbers => {'$all' => [1,2,3].to_set})[:numbers].should == {'$all' => [1,2,3]}
166
+ CriteriaHash.new(:numbers => {'$any' => [1,2,3].to_set})[:numbers].should == {'$any' => [1,2,3]}
167
+ end
168
+ end
169
+
170
+ context "with string ids for string keys" do
171
+ setup do
172
+ @id = BSON::ObjectId.new
173
+ @room_id = BSON::ObjectId.new
174
+ @criteria = CriteriaHash.new(:_id => @id.to_s, :room_id => @room_id.to_s)
175
+ end
176
+
177
+ should "leave string ids as strings" do
178
+ @criteria[:_id].should == @id.to_s
179
+ @criteria[:room_id].should == @room_id.to_s
180
+ @criteria[:_id].should be_instance_of(String)
181
+ @criteria[:room_id].should be_instance_of(String)
182
+ end
183
+ end
184
+
185
+ context "with string ids for object id keys" do
186
+ setup do
187
+ @id = BSON::ObjectId.new
188
+ @room_id = BSON::ObjectId.new
189
+ end
190
+
191
+ should "convert strings to object ids" do
192
+ criteria = CriteriaHash.new({:_id => @id.to_s, :room_id => @room_id.to_s}, :object_ids => [:_id, :room_id])
193
+ criteria[:_id].should == @id
194
+ criteria[:room_id].should == @room_id
195
+ criteria[:_id].should be_instance_of(BSON::ObjectId)
196
+ criteria[:room_id].should be_instance_of(BSON::ObjectId)
197
+ end
198
+
199
+ should "convert :id with string value to object id value" do
200
+ criteria = CriteriaHash.new({:id => @id.to_s}, :object_ids => [:_id])
201
+ criteria[:_id].should == @id
202
+ end
203
+ end
204
+
205
+ context "with string ids for object id keys (nested)" do
206
+ setup do
207
+ @id1 = BSON::ObjectId.new
208
+ @id2 = BSON::ObjectId.new
209
+ @criteria = CriteriaHash.new({:_id => {'$in' => [@id1.to_s, @id2.to_s]}}, :object_ids => [:_id])
210
+ end
211
+
212
+ should "convert strings to object ids" do
213
+ @criteria[:_id].should == {'$in' => [@id1, @id2]}
214
+ end
215
+ end
216
+
217
+ context "#merge" do
218
+ should "work when no keys match" do
219
+ c1 = CriteriaHash.new(:foo => 'bar')
220
+ c2 = CriteriaHash.new(:baz => 'wick')
221
+ c1.merge(c2).should == CriteriaHash.new(:foo => 'bar', :baz => 'wick')
222
+ end
223
+
224
+ should "turn matching keys with simple values into array" do
225
+ c1 = CriteriaHash.new(:foo => 'bar')
226
+ c2 = CriteriaHash.new(:foo => 'baz')
227
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
228
+ end
229
+
230
+ should "unique matching key values" do
231
+ c1 = CriteriaHash.new(:foo => 'bar')
232
+ c2 = CriteriaHash.new(:foo => 'bar')
233
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar]})
234
+ end
235
+
236
+ should "correctly merge arrays and non-arrays" do
237
+ c1 = CriteriaHash.new(:foo => 'bar')
238
+ c2 = CriteriaHash.new(:foo => %w[bar baz])
239
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
240
+ c2.merge(c1).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
241
+ end
242
+
243
+ should "be able to merge two modifier hashes" do
244
+ c1 = CriteriaHash.new('$in' => [1, 2])
245
+ c2 = CriteriaHash.new('$in' => [2, 3])
246
+ c1.merge(c2).should == CriteriaHash.new('$in' => [1, 2, 3])
247
+ end
248
+
249
+ should "merge matching keys with a single modifier" do
250
+ c1 = CriteriaHash.new(:foo => {'$in' => [1, 2, 3]})
251
+ c2 = CriteriaHash.new(:foo => {'$in' => [1, 4, 5]})
252
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => [1, 2, 3, 4, 5]})
253
+ end
254
+
255
+ should "merge matching keys with multiple modifiers" do
256
+ c1 = CriteriaHash.new(:foo => {'$in' => [1, 2, 3]})
257
+ c2 = CriteriaHash.new(:foo => {'$all' => [1, 4, 5]})
258
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => [1, 2, 3], '$all' => [1, 4, 5]})
259
+ end
260
+
261
+ should "not update mergee" do
262
+ c1 = CriteriaHash.new(:foo => 'bar')
263
+ c2 = CriteriaHash.new(:foo => 'baz')
264
+ c1.merge(c2).should_not equal(c1)
265
+ c1[:foo].should == 'bar'
266
+ end
267
+ end
268
+
269
+ context "#merge!" do
270
+ should "merge and replace" do
271
+ c1 = CriteriaHash.new(:foo => 'bar')
272
+ c2 = CriteriaHash.new(:foo => 'baz')
273
+ c1.merge!(c2)
274
+ c1[:foo].should == {'$in' => ['bar', 'baz']}
275
+ end
276
+ end
277
+
278
+ context "#simple?" do
279
+ should "be true if only filtering by _id" do
280
+ CriteriaHash.new(:_id => 'id').should be_simple
281
+ end
282
+
283
+ should "be true if only filtering by Sci" do
284
+ CriteriaHash.new(:_id => 'id', :_type => 'Foo').should be_simple
285
+ end
286
+
287
+ should "be false if querying by anthing other than _id/Sci" do
288
+ CriteriaHash.new(:foo => 'bar').should_not be_simple
289
+ end
290
+
291
+ should "be false if querying only by _type" do
292
+ CriteriaHash.new(:_type => 'Foo').should_not be_simple
293
+ end
294
+ end
295
+ end
296
+ end