plucky 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.project
2
+ log
3
+ *.gem
4
+ Gemfile.lock
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - ree
5
+ - 1.9.2
6
+ - 1.9.3
7
+ notifications:
8
+ email: false
9
+ bundler_args: --without debug
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'bson_ext', '~> 1.5'
5
+ gem 'rake'
6
+
7
+ group(:debug) do
8
+ platforms(:mri_18) { gem 'ruby-debug' }
9
+ platforms(:mri_19) { gem 'ruby-debug19' }
10
+ end
11
+
12
+ group(:test) do
13
+ gem 'shoulda', '~> 2.11'
14
+ gem 'jnunemaker-matchy', '~> 0.4.0', :require => 'matchy'
15
+ gem 'mocha', '~> 0.9.8'
16
+ gem 'log_buddy'
17
+ end
@@ -0,0 +1,101 @@
1
+ # Plucky
2
+
3
+ Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).
4
+
5
+ ## Install
6
+
7
+ ```
8
+ gem install plucky
9
+ ```
10
+
11
+ ## Examples
12
+
13
+ ```ruby
14
+ connection = Mongo::Connection.new
15
+ db = connection.db('test')
16
+ collection = db['users']
17
+ collection.remove # clear out the collection
18
+
19
+ collection.insert({'_id' => 'chris', 'age' => 26, 'name' => 'Chris'})
20
+ collection.insert({'_id' => 'steve', 'age' => 29, 'name' => 'Steve'})
21
+ collection.insert({'_id' => 'john', 'age' => 28, 'name' => 'John'})
22
+
23
+ # initialize query with collection
24
+ query = Plucky::Query.new(collection)
25
+
26
+ puts 'Querying'
27
+ pp query.where(:name => 'John').first
28
+ pp query.first(:name => 'John')
29
+ pp query.where(:name => 'John').all
30
+ pp query.all(:name => 'John')
31
+
32
+ puts 'Find by _id'
33
+ pp query.find('chris')
34
+ pp query.find('chris', 'steve')
35
+ pp query.find(['chris', 'steve'])
36
+
37
+ puts 'Sort'
38
+ pp query.sort(:age).all
39
+ pp query.sort(:age.asc).all # same as above
40
+ pp query.sort(:age.desc).all
41
+ pp query.sort(:age).last # steve
42
+
43
+ puts 'Counting'
44
+ pp query.count # 3
45
+ pp query.size # 3
46
+ pp query.count(:name => 'John') # 1
47
+ pp query.where(:name => 'John').count # 1
48
+ pp query.where(:name => 'John').size # 1
49
+
50
+ puts 'Distinct'
51
+ pp query.distinct(:age) # [26, 29, 28]
52
+
53
+ puts 'Select only certain fields'
54
+ pp query.fields(:age).find('chris') # {"_id"=>"chris", "age"=>26}
55
+ pp query.only(:age).find('chris') # {"_id"=>"chris", "age"=>26}
56
+ pp query.ignore(:name).find('chris') # {"_id"=>"chris", "age"=>26}
57
+
58
+ puts 'Pagination, yeah we got that'
59
+ pp query.sort(:age).paginate(:per_page => 1, :page => 2)
60
+ pp query.sort(:age).per_page(1).paginate(:page => 2)
61
+
62
+ pp query.sort(:age).limit(2).to_a # [chris, john]
63
+ pp query.sort(:age).skip(1).limit(2).to_a # [john, steve]
64
+ pp query.sort(:age).offset(1).limit(2).to_a # [john, steve]
65
+
66
+ puts 'Using a cursor'
67
+ cursor = query.find_each(:sort => :age) do |doc|
68
+ pp doc
69
+ end
70
+ pp cursor
71
+
72
+ puts 'Symbol Operators'
73
+ pp query.where(:age.gt => 28).count # 1 (steve)
74
+ pp query.where(:age.lt => 28).count # 1 (chris)
75
+ pp query.where(:age.in => [26, 28]).to_a # [chris, john]
76
+ pp query.where(:age.nin => [26, 28]).to_a # [steve]
77
+
78
+ puts 'Removing'
79
+ query.remove(:name => 'John')
80
+ pp query.count # 2
81
+ query.where(:name => 'Chris').remove
82
+ pp query.count # 1
83
+ query.remove
84
+ pp query.count # 0
85
+ ```
86
+
87
+ ## Help
88
+
89
+ https://groups.google.com/forum/#!forum/mongomapper
90
+
91
+ ## Contributing
92
+
93
+ * Fork the project.
94
+ * Make your feature addition or bug fix.
95
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
96
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
97
+ * Send me a pull request. Bonus points for topic branches.
98
+
99
+ ## Copyright
100
+
101
+ Copyright (c) 2010 John Nunemaker. See LICENSE for details.
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require File.expand_path('../lib/plucky/version', __FILE__)
5
+
6
+ require 'bundler'
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ namespace :test do
10
+ Rake::TestTask.new(:all) do |test|
11
+ test.libs << 'lib' << 'test'
12
+ test.pattern = 'test/**/test_*.rb'
13
+ test.verbose = true
14
+ end
15
+ end
16
+
17
+ task :test do
18
+ Rake::Task['test:all'].invoke
19
+ end
20
+
21
+ task :default => :test
@@ -0,0 +1,80 @@
1
+ require 'pp'
2
+ require 'pathname'
3
+ require 'rubygems'
4
+
5
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
6
+ lib_path = root_path.join('lib')
7
+ $:.unshift(lib_path)
8
+ require 'plucky'
9
+
10
+ connection = Mongo::Connection.new
11
+ db = connection.db('test')
12
+ collection = db['users']
13
+ collection.remove # clear out the collection
14
+
15
+ collection.insert({'_id' => 'chris', 'age' => 26, 'name' => 'Chris'})
16
+ collection.insert({'_id' => 'steve', 'age' => 29, 'name' => 'Steve'})
17
+ collection.insert({'_id' => 'john', 'age' => 28, 'name' => 'John'})
18
+
19
+ # initialize query with collection
20
+ query = Plucky::Query.new(collection)
21
+
22
+ puts 'Querying'
23
+ pp query.where(:name => 'John').first
24
+ pp query.first(:name => 'John')
25
+ pp query.where(:name => 'John').all
26
+ pp query.all(:name => 'John')
27
+
28
+ puts 'Find by _id'
29
+ pp query.find('chris')
30
+ pp query.find('chris', 'steve')
31
+ pp query.find(['chris', 'steve'])
32
+
33
+ puts 'Sort'
34
+ pp query.sort(:age).all
35
+ pp query.sort(:age.asc).all # same as above
36
+ pp query.sort(:age.desc).all
37
+ pp query.sort(:age).last # steve
38
+
39
+ puts 'Counting'
40
+ pp query.count # 3
41
+ pp query.size # 3
42
+ pp query.count(:name => 'John') # 1
43
+ pp query.where(:name => 'John').count # 1
44
+ pp query.where(:name => 'John').size # 1
45
+
46
+ puts 'Distinct'
47
+ pp query.distinct(:age) # [26, 29, 28]
48
+
49
+ puts 'Select only certain fields'
50
+ pp query.fields(:age).find('chris') # {"_id"=>"chris", "age"=>26}
51
+ pp query.only(:age).find('chris') # {"_id"=>"chris", "age"=>26}
52
+ pp query.ignore(:name).find('chris') # {"_id"=>"chris", "age"=>26}
53
+
54
+ puts 'Pagination, yeah we got that'
55
+ pp query.sort(:age).paginate(:per_page => 1, :page => 2)
56
+ pp query.sort(:age).per_page(1).paginate(:page => 2)
57
+
58
+ pp query.sort(:age).limit(2).to_a # [chris, john]
59
+ pp query.sort(:age).skip(1).limit(2).to_a # [john, steve]
60
+ pp query.sort(:age).offset(1).limit(2).to_a # [john, steve]
61
+
62
+ puts 'Using a cursor'
63
+ cursor = query.find_each(:sort => :age) do |doc|
64
+ pp doc
65
+ end
66
+ pp cursor
67
+
68
+ puts 'Symbol Operators'
69
+ pp query.where(:age.gt => 28).count # 1 (steve)
70
+ pp query.where(:age.lt => 28).count # 1 (chris)
71
+ pp query.where(:age.in => [26, 28]).to_a # [chris, john]
72
+ pp query.where(:age.nin => [26, 28]).to_a # [steve]
73
+
74
+ puts 'Removing'
75
+ query.remove(:name => 'John')
76
+ pp query.count # 2
77
+ query.where(:name => 'Chris').remove
78
+ pp query.count # 1
79
+ query.remove
80
+ pp query.count # 0
@@ -10,16 +10,8 @@ require 'plucky/pagination'
10
10
  module Plucky
11
11
  autoload :Version, 'plucky/version'
12
12
 
13
- # Array of methods that actually perform queries
14
- Methods = [
15
- :where, :filter, :limit, :skip, :offset, :sort, :order,
16
- :fields, :ignore, :only,
17
- :each, :find_each,
18
- :count, :size, :distinct,
19
- :last, :first, :all, :paginate,
20
- :exists?, :exist?, :empty?,
21
- :to_a, :remove,
22
- ]
13
+ # Array of finder DSL methods to delegate
14
+ Methods = Plucky::Query::DSL.instance_methods.sort.map(&:to_sym)
23
15
 
24
16
  def self.to_object_id(value)
25
17
  return value if value.is_a?(BSON::ObjectId)
@@ -3,6 +3,8 @@ module Plucky
3
3
  class CriteriaHash
4
4
  attr_reader :source, :options
5
5
 
6
+ NestingOperators = [:$or, :$and, :$nor]
7
+
6
8
  def initialize(hash={}, options={})
7
9
  @source, @options = {}, options
8
10
  hash.each { |key, value| self[key] = value }
@@ -119,8 +121,15 @@ module Plucky
119
121
  def normalized_value(parent_key, key, value)
120
122
  case value
121
123
  when Array, Set
122
- value.map! { |v| Plucky.to_object_id(v) } if object_id?(parent_key)
123
- parent_key == key && ![:$or, :$and].include?(key) ? {'$in' => value.to_a} : value.to_a
124
+ if object_id?(parent_key)
125
+ value.map { |v| Plucky.to_object_id(v) }
126
+ elsif NestingOperators.include?(key)
127
+ value.map { |v| CriteriaHash.new(v, options).to_hash }
128
+ elsif parent_key == key
129
+ {'$in' => value.to_a}
130
+ else
131
+ value.to_a
132
+ end
124
133
  when Time
125
134
  value.utc
126
135
  when String
@@ -136,4 +145,4 @@ module Plucky
136
145
  end
137
146
  end
138
147
  end
139
- end
148
+ end
@@ -34,138 +34,148 @@ module Plucky
34
34
  self
35
35
  end
36
36
 
37
- def per_page(limit=nil)
38
- return @per_page || 25 if limit.nil?
39
- @per_page = limit
40
- self
41
- end
37
+ # finder DSL methods to delegate to your model if you're building an ODM
38
+ # e.g. MyModel.last needs to be equivalent to MyModel.query.last
39
+ module DSL
40
+ def per_page(limit=nil)
41
+ return @per_page || 25 if limit.nil?
42
+ @per_page = limit
43
+ self
44
+ end
42
45
 
43
- def paginate(opts={})
44
- page = opts.delete(:page)
45
- limit = opts.delete(:per_page) || per_page
46
- query = clone.amend(opts)
47
- paginator = Pagination::Paginator.new(query.count, page, limit)
48
- query.amend(:limit => paginator.limit, :skip => paginator.skip).all.tap do |docs|
49
- docs.extend(Pagination::Decorator)
50
- docs.paginator(paginator)
46
+ def paginate(opts={})
47
+ page = opts.delete(:page)
48
+ limit = opts.delete(:per_page) || per_page
49
+ query = clone.amend(opts)
50
+ paginator = Pagination::Paginator.new(query.count, page, limit)
51
+ query.amend(:limit => paginator.limit, :skip => paginator.skip).all.tap do |docs|
52
+ docs.extend(Pagination::Decorator)
53
+ docs.paginator(paginator)
54
+ end
51
55
  end
52
- end
53
56
 
54
- def find_each(opts={})
55
- query = clone.amend(opts)
56
- query.collection.find(query.criteria.to_hash, query.options.to_hash)
57
- end
57
+ def find_each(opts={}, &block)
58
+ query = clone.amend(opts)
59
+ cursor = query.collection.find(query.criteria.to_hash, query.options.to_hash)
60
+ if block_given?
61
+ cursor.each { |doc| yield doc }
62
+ cursor.rewind!
63
+ end
64
+ cursor
65
+ end
58
66
 
59
- def find_one(opts={})
60
- query = clone.amend(opts)
61
- query.collection.find_one(query.criteria.to_hash, query.options.to_hash)
62
- end
67
+ def find_one(opts={})
68
+ query = clone.amend(opts)
69
+ query.collection.find_one(query.criteria.to_hash, query.options.to_hash)
70
+ end
63
71
 
64
- def find(*ids)
65
- return nil if ids.empty?
66
- if ids.size == 1 && !ids[0].is_a?(Array)
67
- first(:_id => ids[0])
68
- else
69
- all(:_id => ids.flatten)
72
+ def find(*ids)
73
+ return nil if ids.empty?
74
+ if ids.size == 1 && !ids[0].is_a?(Array)
75
+ first(:_id => ids[0])
76
+ else
77
+ all(:_id => ids.flatten)
78
+ end
70
79
  end
71
- end
72
80
 
73
- def all(opts={})
74
- find_each(opts).to_a
75
- end
81
+ def all(opts={})
82
+ find_each(opts).to_a
83
+ end
76
84
 
77
- def first(opts={})
78
- find_one(opts)
79
- end
85
+ def first(opts={})
86
+ find_one(opts)
87
+ end
80
88
 
81
- def last(opts={})
82
- clone.amend(opts).reverse.find_one
83
- end
89
+ def last(opts={})
90
+ clone.amend(opts).reverse.find_one
91
+ end
84
92
 
85
- def each
86
- find_each.each { |doc| yield(doc) }
87
- end
93
+ def each(&block)
94
+ find_each(&block)
95
+ end
88
96
 
89
- def remove(opts={}, driver_opts={})
90
- query = clone.amend(opts)
91
- query.collection.remove(query.criteria.to_hash, driver_opts)
92
- end
97
+ def remove(opts={}, driver_opts={})
98
+ query = clone.amend(opts)
99
+ query.collection.remove(query.criteria.to_hash, driver_opts)
100
+ end
93
101
 
94
- def update(document, driver_opts={})
95
- query = clone
96
- query.collection.update(query.criteria.to_hash, document, driver_opts)
97
- end
102
+ def count(opts={})
103
+ find_each(opts).count
104
+ end
98
105
 
99
- def count(opts={})
100
- find_each(opts).count
101
- end
106
+ def size
107
+ count
108
+ end
102
109
 
103
- def size
104
- count
105
- end
110
+ def distinct(key, opts = {})
111
+ query = clone.amend(opts)
112
+ query.collection.distinct(key, query.criteria.to_hash)
113
+ end
106
114
 
107
- def distinct(key, opts = {})
108
- query = clone.amend(opts)
109
- query.collection.distinct(key, query.criteria.to_hash)
110
- end
115
+ def fields(*args)
116
+ clone.tap { |query| query.options[:fields] = *args }
117
+ end
111
118
 
112
- def amend(opts={})
113
- opts.each { |key, value| self[key] = value }
114
- self
115
- end
119
+ def ignore(*args)
120
+ set_fields(args, 0)
121
+ end
116
122
 
117
- def fields(*args)
118
- clone.tap { |query| query.options[:fields] = *args }
119
- end
123
+ def only(*args)
124
+ set_fields(args, 1)
125
+ end
120
126
 
121
- def ignore(*args)
122
- set_fields(args, 0)
123
- end
127
+ def limit(count=nil)
128
+ clone.tap { |query| query.options[:limit] = count }
129
+ end
124
130
 
125
- def only(*args)
126
- set_fields(args, 1)
127
- end
131
+ def reverse
132
+ clone.tap do |query|
133
+ query[:sort] = query[:sort].map do |s|
134
+ [s[0], -s[1]]
135
+ end unless query.options[:sort].nil?
136
+ end
137
+ end
128
138
 
129
- def limit(count=nil)
130
- clone.tap { |query| query.options[:limit] = count }
131
- end
139
+ def skip(count=nil)
140
+ clone.tap { |query| query.options[:skip] = count }
141
+ end
142
+ alias offset skip
132
143
 
133
- def reverse
134
- clone.tap do |query|
135
- query[:sort] = query[:sort].map do |s|
136
- [s[0], -s[1]]
137
- end unless query.options[:sort].nil?
144
+ def sort(*args)
145
+ clone.tap { |query| query.options[:sort] = *args }
138
146
  end
139
- end
147
+ alias order sort
140
148
 
141
- def skip(count=nil)
142
- clone.tap { |query| query.options[:skip] = count }
143
- end
144
- alias offset skip
149
+ def where(hash={})
150
+ clone.tap do |query|
151
+ query.criteria.merge!(CriteriaHash.new(hash))
152
+ end
153
+ end
154
+ alias filter where
145
155
 
146
- def sort(*args)
147
- clone.tap { |query| query.options[:sort] = *args }
148
- end
149
- alias order sort
156
+ def empty?
157
+ count.zero?
158
+ end
150
159
 
151
- def where(hash={})
152
- clone.tap do |query|
153
- query.criteria.merge!(CriteriaHash.new(hash))
160
+ def exists?(options={})
161
+ !count(options).zero?
154
162
  end
155
- end
156
- alias filter where
163
+ alias :exist? :exists?
157
164
 
158
- def empty?
159
- count.zero?
165
+ def to_a
166
+ find_each.to_a
167
+ end
160
168
  end
169
+ include DSL
161
170
 
162
- def exists?(options={})
163
- !count(options).zero?
171
+ def update(document, driver_opts={})
172
+ query = clone
173
+ query.collection.update(query.criteria.to_hash, document, driver_opts)
164
174
  end
165
- alias :exist? :exists?
166
175
 
167
- def to_a
168
- find_each.to_a
176
+ def amend(opts={})
177
+ opts.each { |key, value| self[key] = value }
178
+ self
169
179
  end
170
180
 
171
181
  def [](key)
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module Plucky
3
- Version = '0.4.4'
4
- end
3
+ Version = '0.5.0'
4
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path('../lib/plucky/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'plucky'
6
+ s.homepage = 'http://github.com/jnunemaker/plucky'
7
+ s.summary = 'Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).'
8
+ s.require_path = 'lib'
9
+ s.homepage = 'http://jnunemaker.github.com/plucky/'
10
+ s.authors = ['John Nunemaker']
11
+ s.email = ['nunemaker@gmail.com']
12
+ s.version = Plucky::Version
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'mongo', '~> 1.5'
21
+ end
@@ -0,0 +1,54 @@
1
+ def growl(title, msg, img)
2
+ %x{growlnotify -m #{ msg.inspect} -t #{title.inspect} --image ~/.watchr/#{img}.png}
3
+ end
4
+
5
+ def form_growl_message(str)
6
+ results = str.split("\n").last
7
+ if results =~ /[1-9]\s(failure|error)s?/
8
+ growl "Test Results", "#{results}", "fail"
9
+ elsif results != ""
10
+ growl "Test Results", "#{results}", "pass"
11
+ end
12
+ end
13
+
14
+ def run(cmd)
15
+ puts(cmd)
16
+ output = ""
17
+ IO.popen(cmd) do |com|
18
+ com.each_char do |c|
19
+ print c
20
+ output << c
21
+ $stdout.flush
22
+ end
23
+ end
24
+ form_growl_message output
25
+ end
26
+
27
+ def run_test_file(file)
28
+ run %Q(ruby -I"lib:test" -rubygems #{file})
29
+ end
30
+
31
+ def run_all_tests
32
+ run "rake test"
33
+ end
34
+
35
+ def related_test_files(path)
36
+ Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
37
+ end
38
+
39
+ # watch('.*\.rb') { system('clear'); run_all_tests }
40
+ watch('test/helper\.rb') { system('clear'); run_all_tests }
41
+ watch('test/.*test_.*\.rb') { |m| system('clear'); run_test_file(m[0]) }
42
+ watch('lib/.*') { |m| related_test_files(m[0]).each { |file| run_test_file(file) } }
43
+
44
+ watch('examples/.*\.rb') { |m| system('clear'); run "bundle exec ruby #{m[0]}" }
45
+
46
+ # Ctrl-\
47
+ Signal.trap('QUIT') do
48
+ puts " --- Running all tests ---\n\n"
49
+ run_all_tests
50
+ end
51
+
52
+ # Ctrl-C
53
+ Signal.trap('INT') { abort("\n") }
54
+
@@ -1,8 +1,7 @@
1
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'
2
+ require 'bundler'
3
+
4
+ Bundler.require(:default, :test)
6
5
 
7
6
  $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
7
  require 'plucky'
@@ -10,11 +9,7 @@ require 'plucky'
10
9
  require 'fileutils'
11
10
  require 'logger'
12
11
  require 'pp'
13
-
14
- require 'log_buddy'
15
- require 'shoulda'
16
- require 'matchy'
17
- require 'mocha'
12
+ require 'set'
18
13
 
19
14
  log_dir = File.expand_path('../../log', __FILE__)
20
15
  FileUtils.mkdir_p(log_dir)
@@ -42,4 +37,4 @@ end
42
37
 
43
38
  operators = %w{gt lt gte lte ne in nin mod all size exists}
44
39
  operators.delete('size') if RUBY_VERSION >= '1.9.1'
45
- SymbolOperators = operators
40
+ SymbolOperators = operators
@@ -24,6 +24,52 @@ class CriteriaHashTest < Test::Unit::TestCase
24
24
  }
25
25
  end
26
26
 
27
+ context "nested clauses" do
28
+ context "::NestingOperators" do
29
+ should "return array of operators that take nested queries" do
30
+ CriteriaHash::NestingOperators.should == [:$or, :$and, :$nor]
31
+ end
32
+ end
33
+
34
+ CriteriaHash::NestingOperators.each do |operator|
35
+ context "#{operator}" do
36
+ should "work with symbol operators" do
37
+ nested1 = {:age.gt => 12, :age.lt => 20}
38
+ translated1 = {:age => {'$gt' => 12, '$lt' => 20 }}
39
+ nested2 = {:type.nin => ['friend', 'enemy']}
40
+ translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
41
+
42
+ given = {operator.to_s => [nested1, nested2]}
43
+
44
+ CriteriaHash.new(given)[operator].should == [translated1, translated2]
45
+ end
46
+
47
+ should "honor criteria hash options" do
48
+ nested = {:post_id => '4f5ead6378fca23a13000001'}
49
+ translated = {:post_id => BSON::ObjectId.from_string('4f5ead6378fca23a13000001')}
50
+ given = {operator.to_s => [nested]}
51
+
52
+ CriteriaHash.new(given, :object_ids => [:post_id])[operator].should == [translated]
53
+ end
54
+ end
55
+ end
56
+
57
+ context "doubly nested" do
58
+ should "work with symbol operators" do
59
+ nested1 = {:age.gt => 12, :age.lt => 20}
60
+ translated1 = {:age => {'$gt' => 12, '$lt' => 20}}
61
+ nested2 = {:type.nin => ['friend', 'enemy']}
62
+ translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
63
+ nested3 = {'$and' => [nested2]}
64
+ translated3 = {:$and => [translated2]}
65
+
66
+ given = {'$or' => [nested1, nested3]}
67
+
68
+ CriteriaHash.new(given)[:$or].should == [translated1, translated3]
69
+ end
70
+ end
71
+ end
72
+
27
73
  context "#initialize_copy" do
28
74
  setup do
29
75
  @original = CriteriaHash.new({
@@ -158,6 +204,10 @@ class CriteriaHashTest < Test::Unit::TestCase
158
204
  should "not turn value to $in with $and key" do
159
205
  CriteriaHash.new(:$and => [{:numbers => 1}, {:numbers => 2}] )[:$and].should == [{:numbers=>1}, {:numbers=>2}]
160
206
  end
207
+
208
+ should "not turn value to $in with $nor key" do
209
+ CriteriaHash.new(:$nor => [{:numbers => 1}, {:numbers => 2}] )[:$nor].should == [{:numbers=>1}, {:numbers=>2}]
210
+ end
161
211
  end
162
212
 
163
213
  context "with set value" do
@@ -210,12 +260,17 @@ class CriteriaHashTest < Test::Unit::TestCase
210
260
  setup do
211
261
  @id1 = BSON::ObjectId.new
212
262
  @id2 = BSON::ObjectId.new
213
- @criteria = CriteriaHash.new({:_id => {'$in' => [@id1.to_s, @id2.to_s]}}, :object_ids => [:_id])
263
+ @ids = [@id1.to_s, @id2.to_s]
264
+ @criteria = CriteriaHash.new({:_id => {'$in' => @ids}}, :object_ids => [:_id])
214
265
  end
215
266
 
216
267
  should "convert strings to object ids" do
217
268
  @criteria[:_id].should == {'$in' => [@id1, @id2]}
218
269
  end
270
+
271
+ should "not modify original array of string ids" do
272
+ @ids.should == [@id1.to_s, @id2.to_s]
273
+ end
219
274
  end
220
275
 
221
276
  context "#merge" do
@@ -297,4 +352,4 @@ class CriteriaHashTest < Test::Unit::TestCase
297
352
  end
298
353
  end
299
354
  end
300
- end
355
+ end
@@ -7,9 +7,9 @@ class OptionsHashTest < Test::Unit::TestCase
7
7
  should "delegate missing methods to the source hash" do
8
8
  hash = {:limit => 1, :skip => 1}
9
9
  options = OptionsHash.new(hash)
10
- options[:skip].should == 1
11
- options[:limit].should == 1
12
- options.keys.should == [:limit, :skip]
10
+ options[:skip].should == 1
11
+ options[:limit].should == 1
12
+ options.keys.to_set.should == [:limit, :skip].to_set
13
13
  end
14
14
 
15
15
  context "#initialize_copy" do
@@ -72,6 +72,18 @@ class QueryTest < Test::Unit::TestCase
72
72
  cursor = Query.new(@collection).find_each(:order => :name.asc)
73
73
  cursor.to_a.should == [@chris, @john, @steve]
74
74
  end
75
+
76
+ should "yield elements to a block if given" do
77
+ yielded_elements = Set.new
78
+ Query.new(@collection).find_each { |doc| yielded_elements << doc }
79
+ yielded_elements.should == [@chris, @john, @steve].to_set
80
+ end
81
+
82
+ should "be Ruby-like and return a reset cursor if a block is given" do
83
+ cursor = Query.new(@collection).find_each {}
84
+ cursor.should be_instance_of(Mongo::Cursor)
85
+ cursor.next.should be_instance_of(oh.class)
86
+ end
75
87
  end
76
88
 
77
89
  context "#find_one" do
@@ -650,6 +662,18 @@ class QueryTest < Test::Unit::TestCase
650
662
  end
651
663
  docs.should == [@chris, @john, @steve]
652
664
  end
665
+
666
+ should "return a working enumerator" do
667
+ query = Query.new(@collection)
668
+ query.each.methods.map(&:to_sym).include?(:group_by).should be(true)
669
+ query.each.next.class.should == oh.class
670
+ end
671
+
672
+ should "be fulfilled by #find_each" do
673
+ query = Query.new(@collection)
674
+ query.expects(:find_each)
675
+ query.each
676
+ end
653
677
  end
654
678
 
655
679
  context "enumerables" do
@@ -32,14 +32,16 @@ class PluckyTest < Test::Unit::TestCase
32
32
  context "::Methods" do
33
33
  should "return array of methods" do
34
34
  Plucky::Methods.should == [
35
- :where, :filter, :limit, :skip, :offset, :sort, :order,
35
+ :where, :filter,
36
+ :sort, :order, :reverse,
37
+ :paginate, :per_page, :limit, :skip, :offset,
36
38
  :fields, :ignore, :only,
37
- :each, :find_each,
39
+ :each, :find_each, :find_one, :find,
38
40
  :count, :size, :distinct,
39
- :last, :first, :all, :paginate,
41
+ :last, :first, :all, :to_a,
40
42
  :exists?, :exist?, :empty?,
41
- :to_a, :remove,
42
- ]
43
+ :remove,
44
+ ].sort_by(&:to_s)
43
45
  end
44
46
  end
45
47
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plucky
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 4
10
- version: 0.4.4
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Nunemaker
@@ -15,10 +15,10 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-20 00:00:00 Z
18
+ date: 2012-04-20 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: mongo
21
+ type: :runtime
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
@@ -30,8 +30,8 @@ dependencies:
30
30
  - 1
31
31
  - 5
32
32
  version: "1.5"
33
- type: :runtime
34
33
  version_requirements: *id001
34
+ name: mongo
35
35
  description:
36
36
  email:
37
37
  - nunemaker@gmail.com
@@ -42,18 +42,28 @@ extensions: []
42
42
  extra_rdoc_files: []
43
43
 
44
44
  files:
45
+ - .gitignore
46
+ - .travis.yml
47
+ - Gemfile
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - UPGRADES
52
+ - examples/query.rb
53
+ - lib/plucky.rb
45
54
  - lib/plucky/criteria_hash.rb
55
+ - lib/plucky/extensions.rb
46
56
  - lib/plucky/extensions/duplicable.rb
47
57
  - lib/plucky/extensions/symbol.rb
48
- - lib/plucky/extensions.rb
49
58
  - lib/plucky/new_relic.rb
50
59
  - lib/plucky/options_hash.rb
60
+ - lib/plucky/pagination.rb
51
61
  - lib/plucky/pagination/decorator.rb
52
62
  - lib/plucky/pagination/paginator.rb
53
- - lib/plucky/pagination.rb
54
63
  - lib/plucky/query.rb
55
64
  - lib/plucky/version.rb
56
- - lib/plucky.rb
65
+ - plucky.gemspec
66
+ - specs.watchr
57
67
  - test/helper.rb
58
68
  - test/plucky/pagination/test_decorator.rb
59
69
  - test/plucky/pagination/test_paginator.rb
@@ -63,10 +73,7 @@ files:
63
73
  - test/test_plucky.rb
64
74
  - test/test_symbol.rb
65
75
  - test/test_symbol_operator.rb
66
- - LICENSE
67
- - README.rdoc
68
- - UPGRADES
69
- homepage: http://github.com/jnunemaker/plucky
76
+ homepage: http://jnunemaker.github.com/plucky/
70
77
  licenses: []
71
78
 
72
79
  post_install_message:
@@ -99,5 +106,13 @@ rubygems_version: 1.8.10
99
106
  signing_key:
100
107
  specification_version: 3
101
108
  summary: Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).
102
- test_files: []
103
-
109
+ test_files:
110
+ - test/helper.rb
111
+ - test/plucky/pagination/test_decorator.rb
112
+ - test/plucky/pagination/test_paginator.rb
113
+ - test/plucky/test_criteria_hash.rb
114
+ - test/plucky/test_options_hash.rb
115
+ - test/plucky/test_query.rb
116
+ - test/test_plucky.rb
117
+ - test/test_symbol.rb
118
+ - test/test_symbol_operator.rb
@@ -1,19 +0,0 @@
1
- = Plucky
2
-
3
- Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).
4
-
5
- == Install
6
-
7
- $ gem install plucky
8
-
9
- == Note on Patches/Pull Requests
10
-
11
- * Fork the project.
12
- * Make your feature addition or bug fix.
13
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
14
- * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
15
- * Send me a pull request. Bonus points for topic branches.
16
-
17
- == Copyright
18
-
19
- Copyright (c) 2010 John Nunemaker. See LICENSE for details.