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 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