plucky 0.4.4 → 0.5.0

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