can? should only go to db if there are mongoid criteria in the conditions.

Easier to just do a simple comparison on the object in memory
than to search the database.  Also this allows method calls
and other attributes that might not be found in the database.
This commit is contained in:
Tyler Gannon 2010-11-15 19:43:54 -08:00
parent dbcd93e095
commit f6aaa581ef
4 changed files with 43 additions and 28 deletions

View File

@ -10,7 +10,7 @@ Gem::Specification.new do |s|
s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"] s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"]
s.require_path = "lib" s.require_path = "lib"
s.add_development_dependency 'rspec', '~> 2.0.0.beta.22' s.add_development_dependency 'rspec', '~> 2.1.0'
s.add_development_dependency 'rails', '~> 3.0.0' s.add_development_dependency 'rails', '~> 3.0.0'
s.add_development_dependency 'rr', '~> 0.10.11' # 1.0.0 has respond_to? issues: http://github.com/btakita/rr/issues/issue/43 s.add_development_dependency 'rr', '~> 0.10.11' # 1.0.0 has respond_to? issues: http://github.com/btakita/rr/issues/issue/43
s.add_development_dependency 'supermodel', '~> 0.1.4' s.add_development_dependency 'supermodel', '~> 0.1.4'
@ -18,7 +18,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'mongoid', '~> 2.0.0.beta.19' s.add_development_dependency 'mongoid', '~> 2.0.0.beta.19'
s.add_development_dependency 'bson_ext', '~> 1.1' s.add_development_dependency 'bson_ext', '~> 1.1'
s.add_development_dependency 'ruby-debug' # s.add_development_dependency 'ruby-debug'
s.rubyforge_project = s.name s.rubyforge_project = s.name
s.required_rubygems_version = ">= 1.3.4" s.required_rubygems_version = ">= 1.3.4"

View File

@ -36,37 +36,28 @@ module CanCan
end end
# customize to handle Mongoid queries in ability definitions conditions # customize to handle Mongoid queries in ability definitions conditions
class CanDefinition # Mongoid Criteria are simpler to check than normal conditions hashes
def matches_conditions_hash?(subject, conditions = @conditions) # When no conditions are given, true should be returned.
if subject.class.include?(Mongoid::Document) # Mongoid Criteria are simpler to check than normal conditions hashes
if conditions.empty? # When no conditions are given, true should be returned.
# The default CanCan behavior relies on the fact that conditions.all? will return true when conditions is empty # The default CanCan behavior relies on the fact that conditions.all? will return true when conditions is empty
# The way ruby handles all? for empty hashes can be unexpected: # The way ruby handles all? for empty hashes can be unexpected:
# {}.all?{|a| a == 5} # {}.all?{|a| a == 5}
# => true # => true
# {}.all?{|a| a != 5} # {}.all?{|a| a != 5}
# => true # => true
class CanDefinition
def matches_conditions_hash_with_mongoid_subject?(subject, conditions = @conditions)
if subject.class.include?(Mongoid::Document) && conditions.any?{|k,v| !k.kind_of?(Symbol)}
if conditions.empty?
true true
else else
subject.class.where(conditions).include?(subject) # just use Mongoid's where function subject.class.where(conditions).include?(subject) # just use Mongoid's where function
end end
else else
conditions.all? do |name, value| matches_conditions_hash_without_mongoid_subject? subject, conditions
attribute = subject.send(name)
if value.kind_of?(Hash)
if attribute.kind_of? Array
attribute.any? { |element| matches_conditions_hash? element, value }
else
matches_conditions_hash? attribute, value
end
elsif value.kind_of?(Array) || value.kind_of?(Range)
value.include? attribute
else
attribute == value
end
end
end end
end end
alias_method :matches_conditions_hash_without_mongoid_subject?, :matches_conditions_hash?
alias_method :matches_conditions_hash?, :matches_conditions_hash_with_mongoid_subject?
end end

View File

@ -50,6 +50,12 @@ describe CanCan::MongoidAdditions do
end.each(&:drop) end.each(&:drop)
end end
it "should compare properties on mongoid documents with the " do
model = @model_class.new
@ability.can :read, @model_class, :id => model.id
@ability.should be_able_to :read, model
end
it "should return [] when no ability is defined so no records are found" do it "should return [] when no ability is defined so no records are found" do
@model_class.create :title => 'Sir' @model_class.create :title => 'Sir'
@model_class.create :title => 'Lord' @model_class.create :title => 'Lord'
@ -88,6 +94,22 @@ describe CanCan::MongoidAdditions do
@ability.can?(:read, obj2).should == false @ability.can?(:read, obj2).should == false
end end
describe "activates only when there are Criteria in the hash" do
it "Calls where on the model class when there are criteria" do
obj = @model_class.create :title => 'Bird'
@conditions = {:title.nin => ["Fork", "Spoon"]}
@model_class.should_receive(:where).with(@conditions) {[obj]}
@ability.can :read, @model_class, @conditions
@ability.can?(:read, obj)
end
it "Calls the base version if there are no mongoid criteria" do
obj = @model_class.new :title => 'Bird'
@conditions = {:id => obj.id}
@ability.can :read, @model_class, @conditions
@ability.should be_able_to(:read, obj)
end
end
it "should handle :field.nin" do it "should handle :field.nin" do
obj = @model_class.create :title => 'Sir' obj = @model_class.create :title => 'Sir'
@ability.can :read, @model_class, :title.nin => ["Lord", "Madam"] @ability.can :read, @model_class, :title.nin => ["Lord", "Madam"]

View File

@ -5,9 +5,11 @@ require 'supermodel' # shouldn't Bundler do this already?
require 'active_support/all' require 'active_support/all'
require 'matchers' require 'matchers'
require 'cancan/matchers' require 'cancan/matchers'
require 'mongoid'
require 'active_support'
RSpec.configure do |config| RSpec.configure do |config|
config.mock_with :rr config.mock_with :rspec
config.before(:each) do config.before(:each) do
Project.delete_all Project.delete_all
Category.delete_all Category.delete_all