Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

compose two FinderOptions objects to get their intersection

  • Loading branch information...
commit cf5c8cd7fc3cc505ebb22b9d882c9e7090980003 1 parent eeec9ea
@fauxparse fauxparse authored
View
90 lib/mongo_mapper/finder_options.rb
@@ -52,6 +52,28 @@ def options
def to_a
[criteria, options]
end
+
+ def compose(other)
+ return other.dup if criteria.empty? && options.empty?
+
+ hash = criteria.merge options
+ c, o = other.criteria, other.options
+
+ hash[:fields] = (Array(hash[:fields]) | o[:fields]) if o[:fields]
+ hash[:skip] = o[:skip] if o[:skip]
+ hash[:limit] = o[:limit] if o[:limit]
+ hash[:sort] = compose_sort_order(hash[:sort], o[:sort]) if o[:sort]
+
+ c.each_pair do |key, value|
+ if hash[key].present?
+ hash[key] = compose_criteria(hash[key], value)
+ else
+ hash[key] = value
+ end
+ end
+
+ self.class.new(@model, hash)
+ end
private
def to_mongo_criteria(conditions, parent_key=nil)
@@ -123,5 +145,73 @@ def to_mongo_sort_piece(str)
direction = FinderOptions.normalized_order_direction(direction)
[field, direction]
end
+
+ def compose_sort_order(a, b)
+ result = Array(a).dup
+ b.reverse.each do |field, direction|
+ f = field.to_s
+ result.reject! { |f, d| f.to_s == field }
+ result.unshift [ field, direction ]
+ end
+ result
+ end
+
+ def compose_criteria(a, b)
+ case b
+ when Hash
+ case a
+ when nil then b.dup
+ when Hash
+ result = a.dup
+ b.each_pair do |key, value|
+ case key
+ when '$ne'
+ if result['$ne'] && (result['$ne'] != value)
+ result['$nin'] = Array(result['$nin']) | [ result.delete('$ne'), value ]
+ else
+ result['$ne'] = value
+ end
+ when '$lt', '$lte'
+ # TODO: be smart about $lt and $lte together
+ if result[key] && (result[key] != value)
+ result[key] = [ result[key], value ].min
+ else
+ result[key] = value
+ end
+ when '$gt', '$gte'
+ # TODO: be smart about $gt and $gte together
+ if result[key] && (result[key] != value)
+ result[key] = [ result[key], value ].max
+ else
+ result[key] = value
+ end
+ when '$in'
+ result['$in'] = result.key?('$in') ? (result['$in'] & b['$in']) : b['$in']
+ when '$nin'
+ result['$nin'] = Array(result['$nin']) | value
+ when '$mod'
+ # TODO: find mathematical way of doing this properly
+ result['$mod'] = value
+ when '$all', '$size'
+ # TODO: these should result in empty result sets if they are different
+ result[key] = value
+ else
+ result[key] = case value
+ when Hash then compose_criteria(result[key], value)
+ else value
+ end
+ end
+ end
+ result
+ else
+ compose_criteria({ '$in' => [ a ] }, b)
+ end
+ else
+ case a
+ when Hash then compose_criteria(b, a)
+ else Array(a) & Array(b)
+ end
+ end
+ end
end
end
View
84 test/unit/test_finder_options.rb
@@ -326,4 +326,88 @@ class FinderOptionsTest < Test::Unit::TestCase
end
end
+ context "Composing" do
+ context "criteria" do
+ should "compose two basic criteria correctly" do
+ a = FinderOptions.new PostComment, :username => "Foo"
+ b = FinderOptions.new PostComment, :username => "Bar"
+ a.compose(b).criteria.should == { :username => { '$in' => [] } } # since there is no overlap
+ end
+
+ should "compose two array criteria correctly" do
+ a = FinderOptions.new PostComment, :username => [ "Foo", "Bar" ]
+ b = FinderOptions.new PostComment, :username => [ "Bar", "Baz" ]
+ a.compose(b).criteria.should == { :username => { '$in' => [ "Bar" ] } }
+ end
+
+ should "compose an array and a scalar correctly" do
+ a = FinderOptions.new PostComment, :username => [ "Foo", "Bar" ]
+ b = FinderOptions.new PostComment, :username => "Bar"
+ a.compose(b).criteria.should == { :username => { '$in' => [ "Bar" ] } }
+ b.compose(a).criteria.should == { :username => { '$in' => [ "Bar" ] } }
+ end
+
+ should "compose hash criteria correctly" do
+ a = FinderOptions.new Message, :position => { '$gte' => 4 }
+ b = FinderOptions.new Message, :position => { '$lt' => 8 }
+ a.compose(b).criteria.should == { :position => { '$gte' => 4, '$lt' => 8 } }
+ end
+
+ should "resolve collisions for scalar comparisons" do
+ a = FinderOptions.new Message, :position => { '$lt' => 4, '$gte' => 3 }
+ b = FinderOptions.new Message, :position => { '$lt' => 8, '$gte' => 2 }
+ a.compose(b).criteria.should == { :position => { '$lt' => 4, '$gte' => 3 } }
+ end
+
+ should "convert different $ne criteria to $nin" do
+ a = FinderOptions.new Message, :position => { '$ne' => 3 }
+ b = FinderOptions.new Message, :position => { '$ne' => 2 }
+ a.compose(b).criteria.should == { :position => { '$nin' => [ 3, 2 ] } }
+ end
+
+ should "not convert same $ne criteria to $nin" do
+ a = FinderOptions.new Message, :position => { '$ne' => 2 }
+ b = FinderOptions.new Message, :position => { '$ne' => 2 }
+ a.compose(b).criteria.should == { :position => { '$ne' => 2 } }
+ end
+
+ should "work arbitrarily deep" do
+ a = FinderOptions.new(Room, :foo => { :bar => [1,2,3] })
+ b = FinderOptions.new(Room, :foo => { :bar => { '$lt' => 4 }, :baz => 5 })
+ a.compose(b).criteria.should == { :foo => { :bar => { '$in' => [ 1, 2, 3 ], '$lt' => 4 }, :baz => 5 } }
+ end
+ end
+
+ context "options" do
+ should "compose fields correctly" do
+ a = FinderOptions.new PostComment, :fields => [ :username, :commentable_type, :commentable_id ]
+ b = FinderOptions.new PostComment, :fields => [ :username, :body ]
+ assert_same_elements a.compose(b).options[:fields], [ :username, :body, :commentable_type, :commentable_id ]
+ end
+
+ should "compose limit correctly" do
+ a = FinderOptions.new PostComment, :limit => 10
+ b = FinderOptions.new PostComment, :limit => 5
+ a.compose(b).options[:limit].should == 5
+ end
+
+ should "compose skip correctly" do
+ a = FinderOptions.new PostComment, :skip => 10
+ b = FinderOptions.new PostComment, :skip => 5
+ a.compose(b).options[:skip].should == 5
+ end
+
+ should "compose order correctly" do
+ a = FinderOptions.new PostComment, :order => "created_at ASC"
+ b = FinderOptions.new PostComment, :order => "updated_at DESC"
+ a.compose(b).options[:sort].should == [[ 'updated_at', -1 ], [ 'created_at', 1 ]]
+ end
+
+ should "compose sort correctly" do
+ a = FinderOptions.new PostComment, :sort => [[ 'updated_at', -1 ], [ 'created_at', 1 ]]
+ b = FinderOptions.new PostComment, :sort => [[ 'updated_at', 1 ]]
+ a.compose(b).options[:sort].should == [[ 'updated_at', 1 ], [ 'created_at', 1 ]]
+ end
+ end
+ end
end # FinderOptionsTest
Please sign in to comment.
Something went wrong with that request. Please try again.