Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Switch to rspec.

  • Loading branch information...
commit ce348727cf6b3342f01766748025583dfba2798e 1 parent 3799f65
@jnunemaker jnunemaker authored
View
4 Gemfile
@@ -10,8 +10,6 @@ group(:debug) do
end
group(:test) do
- gem 'shoulda', '~> 2.11'
- gem 'jnunemaker-matchy', '~> 0.4.0', :require => 'matchy'
- gem 'mocha', '~> 0.9.8'
+ gem 'rspec'
gem 'log_buddy'
end
View
20 Rakefile
@@ -1,21 +1,7 @@
-require 'rubygems'
-require 'rake'
-require 'rake/testtask'
-require File.expand_path('../lib/plucky/version', __FILE__)
-
require 'bundler'
Bundler::GemHelper.install_tasks
-namespace :test do
- Rake::TestTask.new(:all) do |test|
- test.libs << 'lib' << 'test'
- test.pattern = 'test/**/test_*.rb'
- test.verbose = true
- end
-end
-
-task :test do
- Rake::Task['test:all'].invoke
-end
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new
-task :default => :test
+task :default => :spec
View
24 test/helper.rb → spec/helper.rb
@@ -1,15 +1,15 @@
+$:.unshift(File.expand_path('../../lib', __FILE__))
+
require 'rubygems'
require 'bundler'
Bundler.require(:default, :test)
-$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'plucky'
require 'fileutils'
require 'logger'
require 'pp'
-require 'set'
log_dir = File.expand_path('../../log', __FILE__)
FileUtils.mkdir_p(log_dir)
@@ -20,14 +20,7 @@
connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => Log)
DB = connection.db('test')
-class Test::Unit::TestCase
- def setup
- DB.collections.map do |collection|
- collection.remove
- collection.drop_indexes
- end
- end
-
+module OrderedHashHelpers
def oh(*args)
BSON::OrderedHash.new.tap do |hash|
args.each { |a| hash[a[0]] = a[1] }
@@ -35,6 +28,17 @@ def oh(*args)
end
end
+RSpec.configure do |c|
+ c.include OrderedHashHelpers
+
+ c.before(:each) do
+ DB.collections.map do |collection|
+ collection.remove
+ collection.drop_indexes
+ end
+ end
+end
+
operators = %w{gt lt gte lte ne in nin mod all size exists}
operators.delete('size') if RUBY_VERSION >= '1.9.1'
SymbolOperators = operators
View
355 spec/plucky/criteria_hash_spec.rb
@@ -0,0 +1,355 @@
+require 'helper'
+
+describe Plucky::CriteriaHash do
+ it "delegates missing methods to the source hash" do
+ hash = {:baz => 'wick', :foo => 'bar'}
+ criteria = described_class.new(hash)
+ criteria[:foo].should == 'bar'
+ criteria[:baz].should == 'wick'
+ criteria.keys.to_set.should == [:baz, :foo].to_set
+ end
+
+ SymbolOperators.each do |operator|
+ it "works with #{operator} symbol operator" do
+ described_class.new(:age.send(operator) => 21)[:age].should == {"$#{operator}" => 21}
+ end
+ end
+
+ it "handles multiple symbol operators on the same field" do
+ described_class.new(:age.gt => 12, :age.lt => 20)[:age].should == {
+ '$gt' => 12, '$lt' => 20
+ }
+ end
+
+ context "nested clauses" do
+ context "::NestingOperators" do
+ it "returns array of operators that take nested queries" do
+ described_class::NestingOperators.should == [:$or, :$and, :$nor]
+ end
+ end
+
+ described_class::NestingOperators.each do |operator|
+ context "#{operator}" do
+ it "works with symbol operators" do
+ nested1 = {:age.gt => 12, :age.lt => 20}
+ translated1 = {:age => {'$gt' => 12, '$lt' => 20 }}
+ nested2 = {:type.nin => ['friend', 'enemy']}
+ translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
+
+ given = {operator.to_s => [nested1, nested2]}
+
+ described_class.new(given)[operator].should == [translated1, translated2]
+ end
+
+ it "honors criteria hash options" do
+ nested = {:post_id => '4f5ead6378fca23a13000001'}
+ translated = {:post_id => BSON::ObjectId.from_string('4f5ead6378fca23a13000001')}
+ given = {operator.to_s => [nested]}
+
+ described_class.new(given, :object_ids => [:post_id])[operator].should == [translated]
+ end
+ end
+ end
+
+ context "doubly nested" do
+ it "works with symbol operators" do
+ nested1 = {:age.gt => 12, :age.lt => 20}
+ translated1 = {:age => {'$gt' => 12, '$lt' => 20}}
+ nested2 = {:type.nin => ['friend', 'enemy']}
+ translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
+ nested3 = {'$and' => [nested2]}
+ translated3 = {:$and => [translated2]}
+
+ given = {'$or' => [nested1, nested3]}
+
+ described_class.new(given)[:$or].should == [translated1, translated3]
+ end
+ end
+ end
+
+ context "#initialize_copy" do
+ before do
+ @original = described_class.new({
+ :comments => {:_id => 1}, :tags => ['mongo', 'ruby'],
+ }, :object_ids => [:_id])
+ @cloned = @original.clone
+ end
+
+ it "duplicates source hash" do
+ @cloned.source.should_not equal(@original.source)
+ end
+
+ it "duplicates options hash" do
+ @cloned.options.should_not equal(@original.options)
+ end
+
+ it "clones duplicable? values" do
+ @cloned[:comments].should_not equal(@original[:comments])
+ @cloned[:tags].should_not equal(@original[:tags])
+ end
+ end
+
+ context "#object_ids=" do
+ it "works with array" do
+ criteria = described_class.new
+ criteria.object_ids = [:_id]
+ criteria.object_ids.should == [:_id]
+ end
+
+ it "flattens multi-dimensional array" do
+ criteria = described_class.new
+ criteria.object_ids = [[:_id]]
+ criteria.object_ids.should == [:_id]
+ end
+
+ it "raises argument error if not array" do
+ expect { described_class.new.object_ids = {} }.to raise_error(ArgumentError)
+ expect { described_class.new.object_ids = nil }.to raise_error(ArgumentError)
+ expect { described_class.new.object_ids = 'foo' }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "#[]=" do
+ it "leaves string values for string keys alone" do
+ criteria = described_class.new
+ criteria[:foo] = 'bar'
+ criteria[:foo].should == 'bar'
+ end
+
+ it "converts string values to object ids for object id keys" do
+ id = BSON::ObjectId.new
+ criteria = described_class.new({}, :object_ids => [:_id])
+ criteria[:_id] = id.to_s
+ criteria[:_id].should == id
+ end
+
+ it "converts sets to arrays" do
+ criteria = described_class.new
+ criteria[:foo] = [1, 2].to_set
+ criteria[:foo].should == {'$in' => [1, 2]}
+ end
+
+ it "converts times to utc" do
+ time = Time.now
+ criteria = described_class.new
+ criteria[:foo] = time
+ criteria[:foo].should be_utc
+ criteria[:foo].should == time.utc
+ end
+
+ it "converts :id to :_id" do
+ criteria = described_class.new
+ criteria[:id] = 1
+ criteria[:_id].should == 1
+ criteria[:id].should be_nil
+ end
+
+ it "works with symbol operators" do
+ criteria = described_class.new
+ criteria[:_id.in] = ['foo']
+ criteria[:_id].should == {'$in' => ['foo']}
+ end
+
+ it "sets each of the conditions pairs" do
+ criteria = described_class.new
+ criteria[:conditions] = {:_id => 'john', :foo => 'bar'}
+ criteria[:_id].should == 'john'
+ criteria[:foo].should == 'bar'
+ end
+ end
+
+ context "with id key" do
+ it "converts to _id" do
+ id = BSON::ObjectId.new
+ criteria = described_class.new(:id => id)
+ criteria[:_id].should == id
+ criteria[:id].should be_nil
+ end
+
+ it "converts id with symbol operator to _id with modifier" do
+ id = BSON::ObjectId.new
+ criteria = described_class.new(:id.ne => id)
+ criteria[:_id].should == {'$ne' => id}
+ criteria[:id].should be_nil
+ end
+ end
+
+ context "with time value" do
+ it "converts to utc if not utc" do
+ described_class.new(:created_at => Time.now)[:created_at].utc?.should be(true)
+ end
+
+ it "leaves utc alone" do
+ described_class.new(:created_at => Time.now.utc)[:created_at].utc?.should be(true)
+ end
+ end
+
+ context "with array value" do
+ it "defaults to $in" do
+ described_class.new(:numbers => [1,2,3])[:numbers].should == {'$in' => [1,2,3]}
+ end
+
+ it "uses existing modifier if present" do
+ described_class.new(:numbers => {'$all' => [1,2,3]})[:numbers].should == {'$all' => [1,2,3]}
+ described_class.new(:numbers => {'$any' => [1,2,3]})[:numbers].should == {'$any' => [1,2,3]}
+ end
+
+ it "does not turn value to $in with $or key" do
+ described_class.new(:$or => [{:numbers => 1}, {:numbers => 2}] )[:$or].should == [{:numbers=>1}, {:numbers=>2}]
+ end
+
+ it "does not turn value to $in with $and key" do
+ described_class.new(:$and => [{:numbers => 1}, {:numbers => 2}] )[:$and].should == [{:numbers=>1}, {:numbers=>2}]
+ end
+
+ it "does not turn value to $in with $nor key" do
+ described_class.new(:$nor => [{:numbers => 1}, {:numbers => 2}] )[:$nor].should == [{:numbers=>1}, {:numbers=>2}]
+ end
+
+ it "defaults to $in even with ObjectId keys" do
+ described_class.new({:mistake_id => [1,2,3]}, :object_ids => [:mistake_id])[:mistake_id].should == {'$in' => [1,2,3]}
+ end
+ end
+
+ context "with set value" do
+ it "defaults to $in and convert to array" do
+ described_class.new(:numbers => [1,2,3].to_set)[:numbers].should == {'$in' => [1,2,3]}
+ end
+
+ it "uses existing modifier if present and convert to array" do
+ described_class.new(:numbers => {'$all' => [1,2,3].to_set})[:numbers].should == {'$all' => [1,2,3]}
+ described_class.new(:numbers => {'$any' => [1,2,3].to_set})[:numbers].should == {'$any' => [1,2,3]}
+ end
+ end
+
+ context "with string ids for string keys" do
+ before do
+ @id = BSON::ObjectId.new
+ @room_id = BSON::ObjectId.new
+ @criteria = described_class.new(:_id => @id.to_s, :room_id => @room_id.to_s)
+ end
+
+ it "leaves string ids as strings" do
+ @criteria[:_id].should == @id.to_s
+ @criteria[:room_id].should == @room_id.to_s
+ @criteria[:_id].should be_instance_of(String)
+ @criteria[:room_id].should be_instance_of(String)
+ end
+ end
+
+ context "with string ids for object id keys" do
+ before do
+ @id = BSON::ObjectId.new
+ @room_id = BSON::ObjectId.new
+ end
+
+ it "converts strings to object ids" do
+ criteria = described_class.new({:_id => @id.to_s, :room_id => @room_id.to_s}, :object_ids => [:_id, :room_id])
+ criteria[:_id].should == @id
+ criteria[:room_id].should == @room_id
+ criteria[:_id].should be_instance_of(BSON::ObjectId)
+ criteria[:room_id].should be_instance_of(BSON::ObjectId)
+ end
+
+ it "converts :id with string value to object id value" do
+ criteria = described_class.new({:id => @id.to_s}, :object_ids => [:_id])
+ criteria[:_id].should == @id
+ end
+ end
+
+ context "with string ids for object id keys (nested)" do
+ before do
+ @id1 = BSON::ObjectId.new
+ @id2 = BSON::ObjectId.new
+ @ids = [@id1.to_s, @id2.to_s]
+ @criteria = described_class.new({:_id => {'$in' => @ids}}, :object_ids => [:_id])
+ end
+
+ it "converts strings to object ids" do
+ @criteria[:_id].should == {'$in' => [@id1, @id2]}
+ end
+
+ it "does not modify original array of string ids" do
+ @ids.should == [@id1.to_s, @id2.to_s]
+ end
+ end
+
+ context "#merge" do
+ it "works when no keys match" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:baz => 'wick')
+ c1.merge(c2).should == described_class.new(:foo => 'bar', :baz => 'wick')
+ end
+
+ it "turns matching keys with simple values into array" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:foo => 'baz')
+ c1.merge(c2).should == described_class.new(:foo => {'$in' => %w[bar baz]})
+ end
+
+ it "uniques matching key values" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:foo => 'bar')
+ c1.merge(c2).should == described_class.new(:foo => {'$in' => %w[bar]})
+ end
+
+ it "correctly merges arrays and non-arrays" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:foo => %w[bar baz])
+ c1.merge(c2).should == described_class.new(:foo => {'$in' => %w[bar baz]})
+ c2.merge(c1).should == described_class.new(:foo => {'$in' => %w[bar baz]})
+ end
+
+ it "is able to merge two modifier hashes" do
+ c1 = described_class.new('$in' => [1, 2])
+ c2 = described_class.new('$in' => [2, 3])
+ c1.merge(c2).should == described_class.new('$in' => [1, 2, 3])
+ end
+
+ it "merges matching keys with a single modifier" do
+ c1 = described_class.new(:foo => {'$in' => [1, 2, 3]})
+ c2 = described_class.new(:foo => {'$in' => [1, 4, 5]})
+ c1.merge(c2).should == described_class.new(:foo => {'$in' => [1, 2, 3, 4, 5]})
+ end
+
+ it "merges matching keys with multiple modifiers" do
+ c1 = described_class.new(:foo => {'$in' => [1, 2, 3]})
+ c2 = described_class.new(:foo => {'$all' => [1, 4, 5]})
+ c1.merge(c2).should == described_class.new(:foo => {'$in' => [1, 2, 3], '$all' => [1, 4, 5]})
+ end
+
+ it "does not update mergee" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:foo => 'baz')
+ c1.merge(c2).should_not equal(c1)
+ c1[:foo].should == 'bar'
+ end
+ end
+
+ context "#merge!" do
+ it "merges and replace" do
+ c1 = described_class.new(:foo => 'bar')
+ c2 = described_class.new(:foo => 'baz')
+ c1.merge!(c2)
+ c1[:foo].should == {'$in' => ['bar', 'baz']}
+ end
+ end
+
+ context "#simple?" do
+ it "returns true if only filtering by _id" do
+ described_class.new(:_id => 'id').should be_simple
+ end
+
+ it "returns true if only filtering by Sci" do
+ described_class.new(:_id => 'id', :_type => 'Foo').should be_simple
+ end
+
+ it "returns false if querying by anthing other than _id/Sci" do
+ described_class.new(:foo => 'bar').should_not be_simple
+ end
+
+ it "returns false if querying only by _type" do
+ described_class.new(:_type => 'Foo').should_not be_simple
+ end
+ end
+end
View
298 spec/plucky/options_hash_spec.rb
@@ -0,0 +1,298 @@
+require 'helper'
+
+describe Plucky::OptionsHash do
+ it "delegates missing methods to the source hash" do
+ hash = {:limit => 1, :skip => 1}
+ options = described_class.new(hash)
+ options[:skip].should == 1
+ options[:limit].should == 1
+ options.keys.to_set.should == [:limit, :skip].to_set
+ end
+
+ describe "#initialize_copy" do
+ before do
+ @original = described_class.new(:fields => {:name => true}, :sort => :name, :limit => 10)
+ @cloned = @original.clone
+ end
+
+ it "duplicates source hash" do
+ @cloned.source.should_not equal(@original.source)
+ end
+
+ it "clones duplicable? values" do
+ @cloned[:fields].should_not equal(@original[:fields])
+ @cloned[:sort].should_not equal(@original[:sort])
+ end
+ end
+
+ describe "#fields?" do
+ it "returns true if fields have been selected" do
+ described_class.new(:fields => :name).fields?.should be(true)
+ end
+
+ it "returns false if no fields have been selected" do
+ described_class.new.fields?.should be(false)
+ end
+ end
+
+ describe "#[]=" do
+ it "converts order to sort" do
+ options = described_class.new(:order => :foo)
+ options[:order].should be_nil
+ options[:sort].should == [['foo', 1]]
+ end
+
+ it "converts select to fields" do
+ options = described_class.new(:select => 'foo')
+ options[:select].should be_nil
+ options[:fields].should == ['foo']
+ end
+
+ it "converts offset to skip" do
+ options = described_class.new(:offset => 1)
+ options[:offset].should be_nil
+ options[:skip].should == 1
+ end
+
+ context ":fields" do
+ before { @options = described_class.new }
+ subject { @options }
+
+ it "defaults to nil" do
+ subject[:fields].should be_nil
+ end
+
+ it "returns nil if empty string" do
+ subject[:fields] = ''
+ subject[:fields].should be_nil
+ end
+
+ it "returns nil if empty array" do
+ subject[:fields] = []
+ subject[:fields].should be_nil
+ end
+
+ it "works with array" do
+ subject[:fields] = %w[one two]
+ subject[:fields].should == %w[one two]
+ end
+
+ # Ruby 1.9.1 was sending array [{:age => 20}],
+ # instead of hash.
+ it "works with array that has one hash" do
+ subject[:fields] = [{:age => 20}]
+ subject[:fields].should == {:age => 20}
+ end
+
+ it "flattens multi-dimensional array" do
+ subject[:fields] = [[:one, :two]]
+ subject[:fields].should == [:one, :two]
+ end
+
+ it "works with symbol" do
+ subject[:fields] = :one
+ subject[:fields].should == [:one]
+ end
+
+ it "works with array of symbols" do
+ subject[:fields] = [:one, :two]
+ subject[:fields].should == [:one, :two]
+ end
+
+ it "works with hash" do
+ subject[:fields] = {:one => 1, :two => -1}
+ subject[:fields].should == {:one => 1, :two => -1}
+ end
+
+ it "converts comma separated list to array" do
+ subject[:fields] = 'one, two'
+ subject[:fields].should == %w[one two]
+ end
+
+ it "converts select" do
+ subject[:select] = 'one, two'
+ subject[:select].should be_nil
+ subject[:fields].should == %w[one two]
+ end
+ end
+
+ context ":limit" do
+ before { @options = described_class.new }
+ subject { @options }
+
+ it "defaults to nil" do
+ subject[:limit].should be_nil
+ end
+
+ it "uses limit provided" do
+ subject[:limit] = 1
+ subject[:limit].should == 1
+ end
+
+ it "converts string to integer" do
+ subject[:limit] = '1'
+ subject[:limit].should == 1
+ end
+ end
+
+ context ":skip" do
+ before { @options = described_class.new }
+ subject { @options }
+
+ it "defaults to nil" do
+ subject[:skip].should be_nil
+ end
+
+ it "uses limit provided" do
+ subject[:skip] = 1
+ subject[:skip].should == 1
+ end
+
+ it "converts string to integer" do
+ subject[:skip] = '1'
+ subject[:skip].should == 1
+ end
+
+ it "returns set from offset" do
+ subject[:offset] = '1'
+ subject[:offset].should be_nil
+ subject[:skip].should == 1
+ end
+ end
+
+ context ":sort" do
+ before { @options = described_class.new }
+ subject { @options }
+
+ it "defaults to nil" do
+ subject[:sort].should be_nil
+ end
+
+ it "works with natural order ascending" do
+ subject[:sort] = {'$natural' => 1}
+ subject[:sort].should == {'$natural' => 1}
+ end
+
+ it "works with natural order descending" do
+ subject[:sort] = {'$natural' => -1}
+ subject[:sort].should =={'$natural' => -1}
+ end
+
+ it "converts single ascending field (string)" do
+ subject[:sort] = 'foo asc'
+ subject[:sort].should == [['foo', 1]]
+
+ subject[:sort] = 'foo ASC'
+ subject[:sort].should == [['foo', 1]]
+ end
+
+ it "converts single descending field (string)" do
+ subject[:sort] = 'foo desc'
+ subject[:sort].should == [['foo', -1]]
+
+ subject[:sort] = 'foo DESC'
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ it "converts multiple fields (string)" do
+ subject[:sort] = 'foo desc, bar asc'
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ it "converts multiple fields and default no direction to ascending (string)" do
+ subject[:sort] = 'foo desc, bar, baz'
+ subject[:sort].should == [['foo', -1], ['bar', 1], ['baz', 1]]
+ end
+
+ it "converts symbol" do
+ subject[:sort] = :name
+ subject[:sort] = [['name', 1]]
+ end
+
+ it "converts operator" do
+ subject[:sort] = :foo.desc
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ it "converts array of operators" do
+ subject[:sort] = [:foo.desc, :bar.asc]
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ it "converts array of symbols" do
+ subject[:sort] = [:first_name, :last_name]
+ subject[:sort] = [['first_name', 1], ['last_name', 1]]
+ end
+
+ it "works with array and one string element" do
+ subject[:sort] = ['foo, bar desc']
+ subject[:sort].should == [['foo', 1], ['bar', -1]]
+ end
+
+ it "works with array of single array" do
+ subject[:sort] = [['foo', -1]]
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ it "works with array of multiple arrays" do
+ subject[:sort] = [['foo', -1], ['bar', 1]]
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ it "compacts nil values in array" do
+ subject[:sort] = [nil, :foo.desc]
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ it "converts array with mix of values" do
+ subject[:sort] = [:foo.desc, 'bar']
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ it "converts id to _id" do
+ subject[:sort] = [:id.asc]
+ subject[:sort].should == [['_id', 1]]
+ end
+
+ it "converts string with $natural correctly" do
+ subject[:sort] = '$natural desc'
+ subject[:sort].should == [['$natural', -1]]
+ end
+ end
+ end
+
+ describe "#merge" do
+ before do
+ @o1 = described_class.new(:skip => 5, :sort => :name)
+ @o2 = described_class.new(:limit => 10, :skip => 15)
+ @merged = @o1.merge(@o2)
+ end
+
+ it "overrides options in first with options in second" do
+ @merged.should == described_class.new(:limit => 10, :skip => 15, :sort => :name)
+ end
+
+ it "returns new instance and not change either of the merged" do
+ @o1[:skip].should == 5
+ @o2[:sort].should be_nil
+ @merged.should_not equal(@o1)
+ @merged.should_not equal(@o2)
+ end
+ end
+
+ describe "#merge!" do
+ before do
+ @o1 = described_class.new(:skip => 5, :sort => :name)
+ @o2 = described_class.new(:limit => 10, :skip => 15)
+ @merged = @o1.merge!(@o2)
+ end
+
+ it "overrides options in first with options in second" do
+ @merged.should == described_class.new(:limit => 10, :skip => 15, :sort => :name)
+ end
+
+ it "just updates the first" do
+ @merged.should equal(@o1)
+ end
+ end
+end
View
18 test/plucky/pagination/test_decorator.rb → spec/plucky/pagination/decorator_spec.rb
@@ -1,34 +1,32 @@
require 'helper'
-class PaginatorTest < Test::Unit::TestCase
- include Plucky::Pagination
-
+describe Plucky::Pagination::Decorator do
context "Object decorated with Decorator with paginator set" do
- setup do
+ before do
@object = [1, 2, 3, 4]
@object_id = @object.object_id
- @paginator = Paginator.new(20, 2, 10)
- @object.extend(Decorator)
+ @paginator = Plucky::Pagination::Paginator.new(20, 2, 10)
+ @object.extend(described_class)
@object.paginator(@paginator)
end
subject { @object }
- should "be able to get paginator" do
+ it "knows paginator" do
subject.paginator.should == @paginator
end
[:total_entries, :current_page, :per_page, :total_pages, :out_of_bounds?,
:previous_page, :next_page, :skip, :limit, :offset].each do |method|
- should "delegate #{method} to paginator" do
+ it "delegates #{method} to paginator" do
subject.send(method).should == @paginator.send(method)
end
end
- should "not interfere with other methods on the object" do
+ it "does not interfere with other methods on the object" do
@object.object_id.should == @object_id
@object.should == [1, 2, 3, 4]
@object.size.should == 4
@object.select { |o| o > 2 }.should == [3, 4]
end
end
-end
+end
View
118 spec/plucky/pagination/paginator_spec.rb
@@ -0,0 +1,118 @@
+require 'helper'
+
+describe Plucky::Pagination::Paginator do
+ describe "#initialize" do
+ context "with total and page" do
+ before { @paginator = described_class.new(20, 2) }
+ subject { @paginator }
+
+ it "sets total" do
+ subject.total_entries.should == 20
+ end
+
+ it "sets page" do
+ subject.current_page.should == 2
+ end
+
+ it "defaults per_page to 25" do
+ subject.per_page.should == 25
+ end
+ end
+
+ context "with total, page and per_page" do
+ before { @paginator = described_class.new(20, 2, 10) }
+ subject { @paginator }
+
+ it "sets total" do
+ subject.total_entries.should == 20
+ end
+
+ it "sets page" do
+ subject.current_page.should == 2
+ end
+
+ it "sets per_page" do
+ subject.per_page.should == 10
+ end
+ end
+
+ context "with string values for total, page and per_page" do
+ before { @paginator = described_class.new('20', '2', '10') }
+ subject { @paginator }
+
+ it "sets total" do
+ subject.total_entries.should == 20
+ end
+
+ it "sets page" do
+ subject.current_page.should == 2
+ end
+
+ it "sets per_page" do
+ subject.per_page.should == 10
+ end
+ end
+
+ context "with page less than 1" do
+ before { @paginator = described_class.new(20, -2, 10) }
+ subject { @paginator }
+
+ it "sets page to 1" do
+ subject.current_page.should == 1
+ end
+ end
+ end
+
+ it "aliases limit to per_page" do
+ described_class.new(30, 2, 30).limit.should == 30
+ end
+
+ it "knows total number of pages" do
+ described_class.new(43, 2, 7).total_pages.should == 7
+ described_class.new(40, 2, 10).total_pages.should == 4
+ end
+
+ describe "#out_of_bounds?" do
+ it "returns true if current_page is greater than total_pages" do
+ described_class.new(2, 3, 1).should be_out_of_bounds
+ end
+
+ it "returns false if current page is less than total_pages" do
+ described_class.new(2, 1, 1).should_not be_out_of_bounds
+ end
+
+ it "returns false if current page equals total_pages" do
+ described_class.new(2, 2, 1).should_not be_out_of_bounds
+ end
+ end
+
+ describe "#previous_page" do
+ it "returns nil if there is no page less than current" do
+ described_class.new(2, 1, 1).previous_page.should be_nil
+ end
+
+ it "returns number less than current page if there is one" do
+ described_class.new(2, 2, 1).previous_page.should == 1
+ end
+ end
+
+ describe "#next_page" do
+ it "returns nil if no page greater than current page" do
+ described_class.new(2, 2, 1).next_page.should be_nil
+ end
+
+ it "returns number greater than current page if there is one" do
+ described_class.new(2, 1, 1).next_page.should == 2
+ end
+ end
+
+ describe "#skip" do
+ it "works" do
+ described_class.new(30, 3, 10).skip.should == 20
+ end
+
+ it "returns aliased to offset for will paginate" do
+ described_class.new(30, 3, 10).offset.should == 20
+ end
+ end
+end
View
839 spec/plucky/query_spec.rb
@@ -0,0 +1,839 @@
+require 'helper'
+
+describe Plucky::Query do
+ before do
+ @chris = oh(['_id', 'chris'], ['age', 26], ['name', 'Chris'])
+ @steve = oh(['_id', 'steve'], ['age', 29], ['name', 'Steve'])
+ @john = oh(['_id', 'john'], ['age', 28], ['name', 'John'])
+ @collection = DB['users']
+ @collection.insert(@chris)
+ @collection.insert(@steve)
+ @collection.insert(@john)
+ end
+
+ context "#initialize" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "defaults options to options hash" do
+ @query.options.should be_instance_of(Plucky::OptionsHash)
+ end
+
+ it "defaults criteria to criteria hash" do
+ @query.criteria.should be_instance_of(Plucky::CriteriaHash)
+ end
+ end
+
+ context "#initialize_copy" do
+ before do
+ @original = described_class.new(@collection)
+ @cloned = @original.clone
+ end
+
+ it "duplicates options" do
+ @cloned.options.should_not equal(@original.options)
+ end
+
+ it "duplicates criteria" do
+ @cloned.criteria.should_not equal(@original.criteria)
+ end
+ end
+
+ context "#[]=" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "sets key on options for option" do
+ subject[:skip] = 1
+ subject[:skip].should == 1
+ end
+
+ it "sets key on criteria for criteria" do
+ subject[:foo] = 'bar'
+ subject[:foo].should == 'bar'
+ end
+ end
+
+ context "#find_each" do
+ it "returns a cursor" do
+ cursor = described_class.new(@collection).find_each
+ cursor.should be_instance_of(Mongo::Cursor)
+ end
+
+ it "works with and normalize criteria" do
+ cursor = described_class.new(@collection).find_each(:id.in => ['john'])
+ cursor.to_a.should == [@john]
+ end
+
+ it "works with and normalize options" do
+ cursor = described_class.new(@collection).find_each(:order => :name.asc)
+ cursor.to_a.should == [@chris, @john, @steve]
+ end
+
+ it "yields elements to a block if given" do
+ yielded_elements = Set.new
+ described_class.new(@collection).find_each { |doc| yielded_elements << doc }
+ yielded_elements.should == [@chris, @john, @steve].to_set
+ end
+
+ it "is Ruby-like and returns a reset cursor if a block is given" do
+ cursor = described_class.new(@collection).find_each {}
+ cursor.should be_instance_of(Mongo::Cursor)
+ cursor.next.should be_instance_of(oh.class)
+ end
+ end
+
+ context "#find_one" do
+ it "works with and normalize criteria" do
+ described_class.new(@collection).find_one(:id.in => ['john']).should == @john
+ end
+
+ it "works with and normalize options" do
+ described_class.new(@collection).find_one(:order => :age.desc).should == @steve
+ end
+ end
+
+ context "#find" do
+ before do
+ @query = described_class.new(@collection)
+ end
+ subject { @query }
+
+ it "works with single id" do
+ @query.find('chris').should == @chris
+ end
+
+ it "works with multiple ids" do
+ @query.find('chris', 'john').should == [@chris, @john]
+ end
+
+ it "works with array of one id" do
+ @query.find(['chris']).should == [@chris]
+ end
+
+ it "works with array of ids" do
+ @query.find(['chris', 'john']).should == [@chris, @john]
+ end
+
+ it "ignores those not found" do
+ @query.find('john', 'frank').should == [@john]
+ end
+
+ it "returns nil for nil" do
+ @query.find(nil).should be_nil
+ end
+
+ it "returns nil for *nil" do
+ @query.find(*nil).should be_nil
+ end
+
+ it "normalizes if using object id" do
+ id = @collection.insert(:name => 'Frank')
+ @query.object_ids([:_id])
+ doc = @query.find(id.to_s)
+ doc['name'].should == 'Frank'
+ end
+ end
+
+ context "#per_page" do
+ it "defaults to 25" do
+ described_class.new(@collection).per_page.should == 25
+ end
+
+ it "is changeable and chainable" do
+ query = described_class.new(@collection)
+ query.per_page(10).per_page.should == 10
+ end
+ end
+
+ context "#paginate" do
+ before do
+ @query = described_class.new(@collection).sort(:age).per_page(1)
+ end
+ subject { @query }
+
+ it "defaults to page 1" do
+ subject.paginate.should == [@chris]
+ end
+
+ it "works with other pages" do
+ subject.paginate(:page => 2).should == [@john]
+ subject.paginate(:page => 3).should == [@steve]
+ end
+
+ it "works with string page number" do
+ subject.paginate(:page => '2').should == [@john]
+ end
+
+ it "allows changing per_page" do
+ subject.paginate(:per_page => 2).should == [@chris, @john]
+ end
+
+ it "decorates return value" do
+ docs = subject.paginate
+ docs.should respond_to(:paginator)
+ docs.should respond_to(:total_entries)
+ end
+
+ it "does not modify the original query" do
+ subject.paginate(:name => 'John')
+ subject[:page].should be_nil
+ subject[:per_page].should be_nil
+ subject[:name].should be_nil
+ end
+
+ context "with options" do
+ before do
+ @result = @query.sort(:age).paginate(:age.gt => 27, :per_page => 10)
+ end
+ subject { @result }
+
+ it "only returns matching" do
+ subject.should == [@john, @steve]
+ end
+
+ it "correctly counts matching" do
+ subject.total_entries.should == 2
+ end
+ end
+ end
+
+ context "#all" do
+ it "works with no arguments" do
+ docs = described_class.new(@collection).all
+ docs.size.should == 3
+ docs.should include(@john)
+ docs.should include(@steve)
+ docs.should include(@chris)
+ end
+
+ it "works with and normalize criteria" do
+ docs = described_class.new(@collection).all(:id.in => ['steve'])
+ docs.should == [@steve]
+ end
+
+ it "works with and normalize options" do
+ docs = described_class.new(@collection).all(:order => :name.asc)
+ docs.should == [@chris, @john, @steve]
+ end
+
+ it "does not modify original query object" do
+ query = described_class.new(@collection)
+ query.all(:name => 'Steve')
+ query[:name].should be_nil
+ end
+ end
+
+ context "#first" do
+ it "works with and normalize criteria" do
+ described_class.new(@collection).first(:age.lt => 29).should == @chris
+ end
+
+ it "works with and normalize options" do
+ described_class.new(@collection).first(:age.lte => 29, :order => :name.desc).should == @steve
+ end
+
+ it "does not modify original query object" do
+ query = described_class.new(@collection)
+ query.first(:name => 'Steve')
+ query[:name].should be_nil
+ end
+ end
+
+ context "#last" do
+ it "works with and normalize criteria" do
+ described_class.new(@collection).last(:age.lte => 29, :order => :name.asc).should == @steve
+ end
+
+ it "works with and normalize options" do
+ described_class.new(@collection).last(:age.lte => 26, :order => :name.desc).should == @chris
+ end
+
+ it "does not modify original query object" do
+ query = described_class.new(@collection)
+ query.last(:name => 'Steve')
+ query[:name].should be_nil
+ end
+ end
+
+ context "#count" do
+ it "works with no arguments" do
+ described_class.new(@collection).count.should == 3
+ end
+
+ it "works with and normalize criteria" do
+ described_class.new(@collection).count(:age.lte => 28).should == 2
+ end
+
+ it "does not modify original query object" do
+ query = described_class.new(@collection)
+ query.count(:name => 'Steve')
+ query[:name].should be_nil
+ end
+ end
+
+ context "#size" do
+ it "works just like count without options" do
+ described_class.new(@collection).size.should == 3
+ end
+ end
+
+ context "#distinct" do
+ before do
+ # same age as John
+ @mark = oh(['_id', 'mark'], ['age', 28], ['name', 'Mark'])
+ @collection.insert(@mark)
+ end
+
+ it "works with just a key" do
+ described_class.new(@collection).distinct(:age).sort.should == [26, 28, 29]
+ end
+
+ it "works with criteria" do
+ described_class.new(@collection).distinct(:age, :age.gt => 26).sort.should == [28, 29]
+ end
+
+ it "does not modify the original query object" do
+ query = described_class.new(@collection)
+ query.distinct(:age, :name => 'Mark').should == [28]
+ query[:name].should be_nil
+ end
+ end
+
+ context "#remove" do
+ it "works with no arguments" do
+ lambda { described_class.new(@collection).remove }.should change { @collection.count }.by(-3)
+ end
+
+ it "works with and normalize criteria" do
+ lambda { described_class.new(@collection).remove(:age.lte => 28) }.should change { @collection.count }
+ end
+
+ it "works with options" do
+ lambda { described_class.new(@collection).remove({:age.lte => 28}, :safe => true) }.should change { @collection.count }
+ end
+
+ it "does not modify original query object" do
+ query = described_class.new(@collection)
+ query.remove(:name => 'Steve')
+ query[:name].should be_nil
+ end
+ end
+
+ context "#update" do
+ before do
+ @query = described_class.new(@collection).where('_id' => 'john')
+ end
+
+ it "works with document" do
+ @query.update('$set' => {'age' => 29})
+ doc = @query.first('_id' => 'john')
+ doc['age'].should be(29)
+ end
+
+ it "works with document and driver options" do
+ @query.update({'$set' => {'age' => 30}}, :multi => true)
+ @query.each do |doc|
+ doc['age'].should be(30)
+ end
+ end
+ end
+
+ context "#[]" do
+ it "returns value if key in criteria (symbol)" do
+ described_class.new(@collection, :count => 1)[:count].should == 1
+ end
+
+ it "returns value if key in criteria (string)" do
+ described_class.new(@collection, :count => 1)['count'].should == 1
+ end
+
+ it "returns nil if key not in criteria" do
+ described_class.new(@collection)[:count].should be_nil
+ end
+ end
+
+ context "#[]=" do
+ before { @query = described_class.new(@collection) }
+
+ it "sets the value of the given criteria key" do
+ @query[:count] = 1
+ @query[:count].should == 1
+ end
+
+ it "overwrites value if key already exists" do
+ @query[:count] = 1
+ @query[:count] = 2
+ @query[:count].should == 2
+ end
+
+ it "normalizes value" do
+ now = Time.now
+ @query[:published_at] = now
+ @query[:published_at].should == now.utc
+ end
+ end
+
+ context "#fields" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.fields(:name).first(:id => 'john').keys.should == ['_id', 'name']
+ end
+
+ it "returns new instance of query" do
+ new_query = subject.fields(:name)
+ new_query.should_not equal(subject)
+ subject[:fields].should be_nil
+ end
+
+ it "works with hash" do
+ subject.fields(:name => 0).
+ first(:id => 'john').keys.sort.
+ should == ['_id', 'age']
+ end
+ end
+
+ context "#ignore" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "includes a list of keys to ignore" do
+ new_query = subject.ignore(:name).first(:id => 'john')
+ new_query.keys.should == ['_id', 'age']
+ end
+ end
+
+ context "#only" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "includes a list of keys with others excluded" do
+ new_query = subject.only(:name).first(:id => 'john')
+ new_query.keys.should == ['_id', 'name']
+ end
+
+ end
+
+ context "#skip" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.skip(2).all(:order => :age).should == [@steve]
+ end
+
+ it "sets skip option" do
+ subject.skip(5).options[:skip].should == 5
+ end
+
+ it "overrides existing skip" do
+ subject.skip(5).skip(10).options[:skip].should == 10
+ end
+
+ it "returns nil for nil" do
+ subject.skip.options[:skip].should be_nil
+ end
+
+ it "returns new instance of query" do
+ new_query = subject.skip(2)
+ new_query.should_not equal(subject)
+ subject[:skip].should be_nil
+ end
+
+ it "aliases to offset" do
+ subject.offset(5).options[:skip].should == 5
+ end
+ end
+
+ context "#limit" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.limit(2).all(:order => :age).should == [@chris, @john]
+ end
+
+ it "sets limit option" do
+ subject.limit(5).options[:limit].should == 5
+ end
+
+ it "overwrites existing limit" do
+ subject.limit(5).limit(15).options[:limit].should == 15
+ end
+
+ it "returns new instance of query" do
+ new_query = subject.limit(2)
+ new_query.should_not equal(subject)
+ subject[:limit].should be_nil
+ end
+ end
+
+ context "#sort" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.sort(:age).all.should == [@chris, @john, @steve]
+ subject.sort(:age.desc).all.should == [@steve, @john, @chris]
+ end
+
+ it "works with symbol operators" do
+ subject.sort(:foo.asc, :bar.desc).options[:sort].should == [['foo', 1], ['bar', -1]]
+ end
+
+ it "works with string" do
+ subject.sort('foo, bar desc').options[:sort].should == [['foo', 1], ['bar', -1]]
+ end
+
+ it "works with just a symbol" do
+ subject.sort(:foo).options[:sort].should == [['foo', 1]]
+ end
+
+ it "works with symbol descending" do
+ subject.sort(:foo.desc).options[:sort].should == [['foo', -1]]
+ end
+
+ it "works with multiple symbols" do
+ subject.sort(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
+ end
+
+ it "returns new instance of query" do
+ new_query = subject.sort(:name)
+ new_query.should_not equal(subject)
+ subject[:sort].should be_nil
+ end
+
+ it "is aliased to order" do
+ subject.order(:foo).options[:sort].should == [['foo', 1]]
+ subject.order(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
+ end
+ end
+
+ context "#reverse" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.sort(:age).reverse.all.should == [@steve, @john, @chris]
+ end
+
+ it "does not error if no sort provided" do
+ expect {
+ subject.reverse
+ }.to_not raise_error
+ end
+
+ it "reverses the sort order" do
+ subject.sort('foo asc, bar desc').
+ reverse.options[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ it "returns new instance of query" do
+ sorted_query = subject.sort(:name)
+ new_query = sorted_query.reverse
+ new_query.should_not equal(sorted_query)
+ sorted_query[:sort].should == [['name', 1]]
+ end
+ end
+
+ context "#amend" do
+ it "normalizes and update options" do
+ described_class.new(@collection).amend(:order => :age.desc).options[:sort].should == [['age', -1]]
+ end
+
+ it "works with simple stuff" do
+ described_class.new(@collection).
+ amend(:foo => 'bar').
+ amend(:baz => 'wick').
+ criteria.should == Plucky::CriteriaHash.new(:foo => 'bar', :baz => 'wick')
+ end
+ end
+
+ context "#where" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ subject.where(:age.lt => 29).where(:name => 'Chris').all.should == [@chris]
+ end
+
+ it "works with literal regexp" do
+ subject.where(:name => /^c/i).all.should == [@chris]
+ end
+
+ it "updates criteria" do
+ subject.
+ where(:moo => 'cow').
+ where(:foo => 'bar').
+ criteria.should == Plucky::CriteriaHash.new(:foo => 'bar', :moo => 'cow')
+ end
+
+ it "gets normalized" do
+ subject.
+ where(:moo => 'cow').
+ where(:foo.in => ['bar']).
+ criteria.should == Plucky::CriteriaHash.new(:moo => 'cow', :foo => {'$in' => ['bar']})
+ end
+
+ it "normalizes merged criteria" do
+ subject.
+ where(:foo => 'bar').
+ where(:foo => 'baz').
+ criteria.should == Plucky::CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
+ end
+
+ it "returns new instance of query" do
+ new_query = subject.where(:name => 'John')
+ new_query.should_not equal(subject)
+ subject[:name].should be_nil
+ end
+ end
+
+ context "#filter" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works the same as where" do
+ subject.filter(:age.lt => 29).filter(:name => 'Chris').all.should == [@chris]
+ end
+ end
+
+ context "#empty?" do
+ it "returns true if empty" do
+ @collection.remove
+ described_class.new(@collection).should be_empty
+ end
+
+ it "returns false if not empty" do
+ described_class.new(@collection).should_not be_empty
+ end
+ end
+
+ context "#exists?" do
+ it "returns true if found" do
+ described_class.new(@collection).exists?(:name => 'John').should be(true)
+ end
+
+ it "returns false if not found" do
+ described_class.new(@collection).exists?(:name => 'Billy Bob').should be(false)
+ end
+ end
+
+ context "#exist?" do
+ it "returns true if found" do
+ described_class.new(@collection).exist?(:name => 'John').should be(true)
+ end
+
+ it "returns false if not found" do
+ described_class.new(@collection).exist?(:name => 'Billy Bob').should be(false)
+ end
+ end
+
+ context "#include?" do
+ it "returns true if included" do
+ described_class.new(@collection).include?(@john).should be(true)
+ end
+
+ it "returns false if not included" do
+ described_class.new(@collection).include?(['_id', 'frankyboy']).should be(false)
+ end
+ end
+
+ context "#to_a" do
+ it "returns all documents the query matches" do
+ described_class.new(@collection).sort(:name).to_a.
+ should == [@chris, @john, @steve]
+
+ described_class.new(@collection).where(:name => 'John').sort(:name).to_a.
+ should == [@john]
+ end
+ end
+
+ context "#each" do
+ it "iterates through matching documents" do
+ docs = []
+ described_class.new(@collection).sort(:name).each do |doc|
+ docs << doc
+ end
+ docs.should == [@chris, @john, @steve]
+ end
+
+ it "returns a working enumerator" do
+ query = described_class.new(@collection)
+ query.each.methods.map(&:to_sym).include?(:group_by).should be(true)
+ query.each.next.class.should == oh.class
+ end
+
+ it "uses #find_each" do
+ query = described_class.new(@collection)
+ query.should_receive(:find_each)
+ query.each
+ end
+ end
+
+ context "enumerables" do
+ it "works" do
+ query = described_class.new(@collection).sort(:name)
+ query.map { |doc| doc['name'] }.should == %w(Chris John Steve)
+ query.collect { |doc| doc['name'] }.should == %w(Chris John Steve)
+ query.detect { |doc| doc['name'] == 'John' }.should == @john
+ query.min { |a, b| a['age'] <=> b['age'] }.should == @chris
+ end
+ end
+
+ context "#object_ids" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "sets criteria's object_ids" do
+ subject.criteria.should_receive(:object_ids=).with([:foo, :bar])
+ subject.object_ids(:foo, :bar)
+ end
+
+ it "returns current object ids if keys argument is empty" do
+ subject.object_ids(:foo, :bar)
+ subject.object_ids.should == [:foo, :bar]
+ end
+ end
+
+ context "#merge" do
+ it "overwrites options" do
+ query1 = described_class.new(@collection, :skip => 5, :limit => 5)
+ query2 = described_class.new(@collection, :skip => 10, :limit => 10)
+ new_query = query1.merge(query2)
+ new_query.options[:skip].should == 10
+ new_query.options[:limit].should == 10
+ end
+
+ it "merges criteria" do
+ query1 = described_class.new(@collection, :foo => 'bar')
+ query2 = described_class.new(@collection, :foo => 'baz', :fent => 'wick')
+ new_query = query1.merge(query2)
+ new_query.criteria[:fent].should == 'wick'
+ new_query.criteria[:foo].should == {'$in' => %w[bar baz]}
+ end
+
+ it "does not affect either of the merged queries" do
+ query1 = described_class.new(@collection, :foo => 'bar', :limit => 5)
+ query2 = described_class.new(@collection, :foo => 'baz', :limit => 10)
+ new_query = query1.merge(query2)
+ query1[:foo].should == 'bar'
+ query1[:limit].should == 5
+ query2[:foo].should == 'baz'
+ query2[:limit].should == 10
+ end
+ end
+
+ context "Criteria/option auto-detection" do
+ it "knows :conditions are criteria" do
+ query = described_class.new(@collection, :conditions => {:foo => 'bar'})
+ query.criteria.should == Plucky::CriteriaHash.new(:foo => 'bar')
+ query.options.keys.should_not include(:conditions)
+ end
+
+ {
+ :fields => ['foo'],
+ :sort => [['foo', 1]],
+ :hint => '',
+ :skip => 0,
+ :limit => 0,
+ :batch_size => 0,
+ :timeout => 0,
+ }.each do |option, value|
+ it "knows #{option} is an option" do
+ query = described_class.new(@collection, option => value)
+ query.options[option].should == value
+ query.criteria.keys.should_not include(option)
+ end
+ end
+
+ it "knows select is an option and remove it from options" do
+ query = described_class.new(@collection, :select => 'foo')
+ query.options[:fields].should == ['foo']
+ query.criteria.keys.should_not include(:select)
+ query.options.keys.should_not include(:select)
+ end
+
+ it "knows order is an option and remove it from options" do
+ query = described_class.new(@collection, :order => 'foo')
+ query.options[:sort].should == [['foo', 1]]
+ query.criteria.keys.should_not include(:order)
+ query.options.keys.should_not include(:order)
+ end
+
+ it "knows offset is an option and remove it from options" do
+ query = described_class.new(@collection, :offset => 0)
+ query.options[:skip].should == 0
+ query.criteria.keys.should_not include(:offset)
+ query.options.keys.should_not include(:offset)
+ end
+
+ it "works with full range of things" do
+ query = described_class.new(@collection, {
+ :foo => 'bar',
+ :baz => true,
+ :sort => [['foo', 1]],
+ :fields => ['foo', 'baz'],
+ :limit => 10,
+ :skip => 10,
+ })
+ query.criteria.should == Plucky::CriteriaHash.new(:foo => 'bar', :baz => true)
+ query.options.should == Plucky::OptionsHash.new({
+ :sort => [['foo', 1]],
+ :fields => ['foo', 'baz'],
+ :limit => 10,
+ :skip => 10,
+ })
+ end
+ end
+
+ it "inspects pretty" do
+ inspect = described_class.new(@collection, :baz => 'wick', :foo => 'bar').inspect
+ inspect.should == '#<Plucky::Query baz: "wick", foo: "bar">'
+ end
+
+ it "delegates simple? to criteria" do
+ query = described_class.new(@collection)
+ query.criteria.should_receive(:simple?)
+ query.simple?
+ end
+
+ it "delegates fields? to options" do
+ query = described_class.new(@collection)
+ query.options.should_receive(:fields?)
+ query.fields?
+ end
+
+ context "#explain" do
+ before { @query = described_class.new(@collection) }
+ subject { @query }
+
+ it "works" do
+ explain = subject.where(:age.lt => 28).explain
+ explain['cursor'].should == 'BasicCursor'
+ explain['nscanned'].should == 3
+ end
+ end
+
+ context "Transforming documents" do
+ before do
+ transformer = lambda { |doc| @user_class.new(doc['_id'], doc['name'], doc['age']) }
+ @user_class = Struct.new(:id, :name, :age)
+ @query = described_class.new(@collection, :transformer => transformer)
+ end
+
+ it "works with find_one" do
+ result = @query.find_one('_id' => 'john')
+ result.should be_instance_of(@user_class)
+ end
+
+ it "works with find_each" do
+ results = @query.find_each
+ results.each do |result|
+ result.should be_instance_of(@user_class)
+ end
+ end
+ end
+end
View
46 spec/plucky_spec.rb
@@ -0,0 +1,46 @@
+require 'helper'
+
+describe Plucky do
+ describe ".to_object_id" do
+ before do
+ @id = BSON::ObjectId.new
+ end
+
+ it "converts nil to nil" do
+ Plucky.to_object_id(nil).should be_nil
+ end
+
+ it "converts blank to nil" do
+ Plucky.to_object_id('').should be_nil
+ end
+
+ it "leaves object id alone" do
+ Plucky.to_object_id(@id).should equal(@id)
+ end
+
+ it "converts string to object id" do
+ Plucky.to_object_id(@id.to_s).should == @id
+ end
+
+ it "not convert string that is not legal object id" do
+ Plucky.to_object_id('foo').should == 'foo'
+ Plucky.to_object_id(1).should == 1
+ end
+ end
+
+ describe "::Methods" do
+ it "returns array of methods" do
+ Plucky::Methods.should == [
+ :where, :filter,
+ :sort, :order, :reverse,
+ :paginate, :per_page, :limit, :skip, :offset,
+ :fields, :ignore, :only,
+ :each, :find_each, :find_one, :find,
+ :count, :size, :distinct,
+ :last, :first, :all, :to_a,
+ :exists?, :exist?, :empty?,
+ :remove,
+ ].sort_by(&:to_s)
+ end
+ end
+end
View
30 test/test_symbol_operator.rb → spec/symbol_operator_spec.rb
@@ -1,55 +1,53 @@
require 'helper'
-class SymbolOperatorTest < Test::Unit::TestCase
+describe SymbolOperator do
context "SymbolOperator" do
- setup { @operator = SymbolOperator.new(:foo, 'in') }
+ before { @operator = SymbolOperator.new(:foo, 'in') }
subject { @operator }
- should "have field" do
+ it "has field" do
subject.field.should == :foo
end
- should "have operator" do
+ it "has operator" do
subject.operator.should == 'in'
end
context "==" do
- should "be true if field and operator are equal" do
+ it "returns true if field and operator are equal" do
SymbolOperator.new(:foo, 'in').should == SymbolOperator.new(:foo, 'in')
end
- should "be false if fields are equal but operators are not" do
+ it "returns false if fields are equal but operators are not" do
SymbolOperator.new(:foo, 'in').should_not == SymbolOperator.new(:foo, 'all')
end
- should "be false if operators are equal but fields are not" do
+ it "returns false if operators are equal but fields are not" do
SymbolOperator.new(:foo, 'in').should_not == SymbolOperator.new(:bar, 'in')
end
- should "be false if neither are equal" do
+ it "returns false if neither are equal" do
SymbolOperator.new(:foo, 'in').should_not == SymbolOperator.new(:bar, 'all')
end
- should "be false if other isn't an symbol operator" do
- assert_nothing_raised do
- SymbolOperator.new(:foo, 'in').should_not == 'foo.in'
- end
+ it "returns false if other isn't an symbol operator" do
+ SymbolOperator.new(:foo, 'in').should_not == 'foo.in'
end
end
context "<=>" do
- should "same field, different operator" do
+ it "returns string comparison of operator for same field, different operator" do
(SymbolOperator.new(:foo, 'in') <=> SymbolOperator.new(:foo, 'all')).should == 1
(SymbolOperator.new(:foo, 'all') <=> SymbolOperator.new(:foo, 'in')).should == -1
end
- should "same field same operator" do
+ it "returns 0 for same field same operator" do
(SymbolOperator.new(:foo, 'in') <=> SymbolOperator.new(:foo, 'in')).should == 0
end
- should "different field" do
+ it "returns 1 for different field" do
(SymbolOperator.new(:foo, 'in') <=> SymbolOperator.new(:bar, 'in')).should == 1
end
end
end
-end
+end
View
9 spec/symbol_spec.rb
@@ -0,0 +1,9 @@
+require 'helper'
+
+describe Symbol do
+ SymbolOperators.each do |operator|
+ it "responds to #{operator}" do
+ :foo.send(operator).should be_instance_of(SymbolOperator)
+ end
+ end
+end
View
120 test/plucky/pagination/test_paginator.rb
@@ -1,120 +0,0 @@
-require 'helper'
-
-class PaginatorTest < Test::Unit::TestCase
- include Plucky::Pagination
-
- context "#initialize" do
- context "with total and page" do
- setup { @paginator = Paginator.new(20, 2) }
- subject { @paginator }
-
- should "set total" do
- subject.total_entries.should == 20
- end
-
- should "set page" do
- subject.current_page.should == 2
- end
-
- should "default per_page to 25" do
- subject.per_page.should == 25
- end
- end
-
- context "with total, page and per_page" do
- setup { @paginator = Paginator.new(20, 2, 10) }
- subject { @paginator }
-
- should "set total" do
- subject.total_entries.should == 20
- end
-
- should "set page" do
- subject.current_page.should == 2
- end
-
- should "set per_page" do
- subject.per_page.should == 10
- end
- end
-
- context "with string values for total, page and per_page" do
- setup { @paginator = Paginator.new('20', '2', '10') }
- subject { @paginator }
-
- should "set total" do
- subject.total_entries.should == 20
- end
-
- should "set page" do
- subject.current_page.should == 2
- end
-
- should "set per_page" do
- subject.per_page.should == 10
- end
- end
-
- context "with page less than 1" do
- setup { @paginator = Paginator.new(20, -2, 10) }
- subject { @paginator }
-
- should "set page to 1" do
- subject.current_page.should == 1
- end
- end
- end
-
- should "alias limit to per_page" do
- Paginator.new(30, 2, 30).limit.should == 30
- end
-
- should "be know total number of pages" do
- Paginator.new(43, 2, 7).total_pages.should == 7
- Paginator.new(40, 2, 10).total_pages.should == 4
- end
-
- context "#out_of_bounds?" do
- should "be true if current_page is greater than total_pages" do
- Paginator.new(2, 3, 1).should be_out_of_bounds
- end
-
- should "be false if current page is less than total_pages" do
- Paginator.new(2, 1, 1).should_not be_out_of_bounds
- end
-
- should "be false if current page equals total_pages" do
- Paginator.new(2, 2, 1).should_not be_out_of_bounds
- end
- end
-
- context "#previous_page" do
- should "be nil if there is no page less than current" do
- Paginator.new(2, 1, 1).previous_page.should be_nil
- end
-
- should "be number less than current page if there is one" do
- Paginator.new(2, 2, 1).previous_page.should == 1
- end
- end
-
- context "#next_page" do
- should "be nil if no page greater than current page" do
- Paginator.new(2, 2, 1).next_page.should be_nil
- end
-
- should "be number greater than current page if there is one" do
- Paginator.new(2, 1, 1).next_page.should == 2
- end
- end
-
- context "#skip" do
- should "work" do
- Paginator.new(30, 3, 10).skip.should == 20
- end
-
- should "be aliased to offset for will paginate" do
- Paginator.new(30, 3, 10).offset.should == 20
- end
- end
-end
View
359 test/plucky/test_criteria_hash.rb
@@ -1,359 +0,0 @@
-require 'helper'
-
-class CriteriaHashTest < Test::Unit::TestCase
- include Plucky
-
- context "Plucky::CriteriaHash" do
- should "delegate missing methods to the source hash" do
- hash = {:baz => 'wick', :foo => 'bar'}
- criteria = CriteriaHash.new(hash)
- criteria[:foo].should == 'bar'
- criteria[:baz].should == 'wick'
- criteria.keys.to_set.should == [:baz, :foo].to_set
- end
-
- SymbolOperators.each do |operator|
- should "work with #{operator} symbol operator" do
- CriteriaHash.new(:age.send(operator) => 21)[:age].should == {"$#{operator}" => 21}
- end
- end
-
- should "handle multiple symbol operators on the same field" do
- CriteriaHash.new(:age.gt => 12, :age.lt => 20)[:age].should == {
- '$gt' => 12, '$lt' => 20
- }
- end
-
- context "nested clauses" do
- context "::NestingOperators" do
- should "return array of operators that take nested queries" do
- CriteriaHash::NestingOperators.should == [:$or, :$and, :$nor]
- end
- end
-
- CriteriaHash::NestingOperators.each do |operator|
- context "#{operator}" do
- should "work with symbol operators" do
- nested1 = {:age.gt => 12, :age.lt => 20}
- translated1 = {:age => {'$gt' => 12, '$lt' => 20 }}
- nested2 = {:type.nin => ['friend', 'enemy']}
- translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
-
- given = {operator.to_s => [nested1, nested2]}
-
- CriteriaHash.new(given)[operator].should == [translated1, translated2]
- end
-
- should "honor criteria hash options" do
- nested = {:post_id => '4f5ead6378fca23a13000001'}
- translated = {:post_id => BSON::ObjectId.from_string('4f5ead6378fca23a13000001')}
- given = {operator.to_s => [nested]}
-
- CriteriaHash.new(given, :object_ids => [:post_id])[operator].should == [translated]
- end
- end
- end
-
- context "doubly nested" do
- should "work with symbol operators" do
- nested1 = {:age.gt => 12, :age.lt => 20}
- translated1 = {:age => {'$gt' => 12, '$lt' => 20}}
- nested2 = {:type.nin => ['friend', 'enemy']}
- translated2 = {:type => {'$nin' => ['friend', 'enemy']}}
- nested3 = {'$and' => [nested2]}
- translated3 = {:$and => [translated2]}
-
- given = {'$or' => [nested1, nested3]}
-
- CriteriaHash.new(given)[