Permalink
Browse files

Add Array-style query syntax

  • Loading branch information...
1 parent 4a8af11 commit b6fcebd3b90ed96a200f844029c9e095af6d73ba @jgaskins committed Jul 1, 2012
@@ -2,6 +2,7 @@
require 'perpetuity/attribute'
require 'perpetuity/validations'
require 'perpetuity/data_injectable'
+require 'perpetuity/mongodb/query'
module Perpetuity
class Mapper
@@ -161,6 +162,11 @@ def self.retrieve criteria={}
Perpetuity::Retrieval.new mapped_class, criteria
end
+ def self.select &block
+ query = data_source.class::Query.new(&block).to_db
+ retrieve query
+ end
+
def self.find id
retrieve(id: id).first
end
@@ -0,0 +1,19 @@
+require 'perpetuity/mongodb/query_attribute'
+
+module Perpetuity
+ class MongoDB
+ class Query
+ def initialize &block
+ @query = instance_exec &block
+ end
+
+ def to_db
+ @query.to_db
+ end
+
+ def method_missing missing_method
+ QueryAttribute.new missing_method
+ end
+ end
+ end
+end
@@ -0,0 +1,45 @@
+require 'perpetuity/mongodb/query_expression'
+
+module Perpetuity
+ class MongoDB
+ class QueryAttribute
+ attr_reader :name
+
+ def initialize name
+ @name = name
+ end
+
+ def == value
+ QueryExpression.new self, :equals, value
+ end
+
+ def < value
+ QueryExpression.new self, :less_than, value
+ end
+
+ def >= value
+ QueryExpression.new self, :gte, value
+ end
+
+ def > value
+ QueryExpression.new self, :greater_than, value
+ end
+
+ def <= value
+ QueryExpression.new self, :lte, value
+ end
+
+ def not_equal? value
+ QueryExpression.new self, :not_equal, value
+ end
+
+ def =~ regexp
+ QueryExpression.new self, :matches, regexp
+ end
+
+ def to_sym
+ name
+ end
+ end
+ end
+end
@@ -0,0 +1,55 @@
+module Perpetuity
+ class MongoDB
+ class QueryExpression
+ attr_accessor :comparator
+
+ def initialize attribute, comparator, value
+ @attribute = attribute
+ @comparator = comparator
+ @value = value
+
+ @attribute = @attribute.to_sym if @attribute.respond_to? :to_sym
+ end
+
+ def to_db
+ send @comparator
+ end
+
+ def equals
+ { @attribute => @value }
+ end
+
+ def function func
+ { @attribute => { func => @value } }
+ end
+
+ def less_than
+ function '$lt'
+ end
+
+ def lte
+ function '$lte'
+ end
+
+ def greater_than
+ function '$gt'
+ end
+
+ def gte
+ function '$gte'
+ end
+
+ def not_equal
+ function '$ne'
+ end
+
+ def in
+ function '$in'
+ end
+
+ def matches
+ { @attribute => @value }
+ end
+ end
+ end
+end
@@ -0,0 +1,39 @@
+$:.unshift('lib').uniq!
+require 'perpetuity/mongodb/query_attribute'
+
+module Perpetuity
+ describe MongoDB::QueryAttribute do
+ let(:attribute) { MongoDB::QueryAttribute.new :attribute_name }
+ subject { attribute }
+
+ its(:name) { should == :attribute_name }
+
+ it 'checks for equality' do
+ (attribute == 1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for less than' do
+ (attribute < 1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for <=' do
+ (attribute <= 1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for greater than' do
+ (attribute > 1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for >=' do
+ (attribute >= 1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for inequality' do
+ attribute.not_equal?(1).should be_a MongoDB::QueryExpression
+ end
+
+ it 'checks for regexp matches' do
+ (attribute =~ /value/).should be_a MongoDB::QueryExpression
+ end
+ end
+end
@@ -0,0 +1,50 @@
+$:.unshift('lib').uniq!
+require 'perpetuity/mongodb/query_expression'
+
+module Perpetuity
+ describe MongoDB::QueryExpression do
+ let(:expression) { MongoDB::QueryExpression.new :attribute, :equals, :value }
+ subject { expression }
+
+ describe 'translation to Mongo expressions' do
+ it 'equality expression' do
+ expression.to_db.should == { attribute: :value }
+ end
+
+ it 'less-than expression' do
+ expression.comparator = :less_than
+ expression.to_db.should == { attribute: { '$lt' => :value } }
+ end
+
+ it 'less-than-or-equal-to expression' do
+ expression.comparator = :lte
+ expression.to_db.should == { attribute: { '$lte' => :value } }
+ end
+
+ it 'greater-than expression' do
+ expression.comparator = :greater_than
+ expression.to_db.should == { attribute: { '$gt' => :value } }
+ end
+
+ it 'greater-than-or-equal-to expression' do
+ expression.comparator = :gte
+ expression.to_db.should == { attribute: { '$gte' => :value } }
+ end
+
+ it 'not-equal' do
+ expression.comparator = :not_equal
+ expression.to_db.should == { attribute: { '$ne' => :value } }
+ end
+
+ it 'checks for inclusion' do
+ expression.comparator = :in
+ expression.to_db.should == { attribute: { '$in' => :value } }
+ end
+
+ it 'checks for regexp matching' do
+ expression.comparator = :matches
+ expression.to_db.should == { attribute: :value }
+ end
+ end
+ end
+end
@@ -0,0 +1,38 @@
+$:.unshift('lib').uniq!
+require 'perpetuity/mongodb/query'
+
+module Perpetuity
+ describe MongoDB::Query do
+ let(:query) { MongoDB::Query }
+
+ it 'generates Mongo equality expressions' do
+ query.new{name == 'Jamie'}.to_db.should == {name: 'Jamie'}
+ end
+
+ it 'generates Mongo less-than expressions' do
+ query.new{quantity < 10}.to_db.should == {quantity: { '$lt' => 10}}
+ end
+
+ it 'generates Mongo less-than-or-equal expressions' do
+ query.new{quantity <= 10}.to_db.should == {quantity: { '$lte' => 10}}
+ end
+
+ it 'generates Mongo greater-than expressions' do
+ query.new{quantity > 10}.to_db.should == {quantity: { '$gt' => 10}}
+ end
+
+ it 'generates Mongo greater-than-or-equal expressions' do
+ query.new{quantity >= 10}.to_db.should == {quantity: { '$gte' => 10}}
+ end
+
+ it 'generates Mongo inequality expressions' do
+ query.new{name.not_equal? 'Jamie'}.to_db.should == {
+ name: {'$ne' => 'Jamie'}
+ }
+ end
+
+ it 'generates Mongo regexp expressions' do
+ query.new{name =~ /Jamie/}.to_db.should == {name: /Jamie/}
+ end
+ end
+end
@@ -134,6 +134,63 @@
retrieved.to_a.should_not be_empty
retrieved.first.title.should == article.title
end
+
+ describe "Array-like syntax" do
+ let(:draft) { Article.new 'Draft', 'draft content', nil, Time.now + 30 }
+ let(:published) { Article.new 'Published', 'content', nil, Time.now - 30, 3 }
+ before do
+ ArticleMapper.insert draft
+ ArticleMapper.insert published
+ end
+
+ it 'selects objects using equality' do
+ selected = ArticleMapper.select { title == 'Published' }
+ selected.map(&:id).should include published.id
+ selected.map(&:id).should_not include draft.id
+ end
+
+ it 'selects objects using greater-than' do
+ selected = ArticleMapper.select { published_at < Time.now }
+ ids = selected.map(&:id)
+ ids.should include published.id
+ ids.should_not include draft.id
+ end
+
+ it 'selects objects using greater-than-or-equal' do
+ selected = ArticleMapper.select { views >= 3 }
+ ids = selected.map(&:id)
+ ids.should include published.id
+ ids.should_not include draft.id
+ end
+
+ it 'selects objects using less-than' do
+ selected = ArticleMapper.select { views < 3 }
+ ids = selected.map(&:id)
+ ids.should include draft.id
+ ids.should_not include published.id
+ end
+
+ it 'selects objects using less-than-or-equal' do
+ selected = ArticleMapper.select { views <= 0 }
+ ids = selected.map(&:id)
+ ids.should include draft.id
+ ids.should_not include published.id
+ end
+
+ it 'selects objects using inequality' do
+ selected = ArticleMapper.select { title.not_equal? 'Draft' }
+ ids = selected.map(&:id)
+ ids.should_not include draft.id
+ ids.should include published.id
+ end
+
+ it 'selects objects using regular expressions' do
+ selected = ArticleMapper.select { title =~ /Pub/ }
+ ids = selected.map(&:id)
+ ids.should include published.id
+ ids.should_not include draft.id
+ end
+ end
end
describe 'pagination' do

0 comments on commit b6fcebd

Please sign in to comment.