allow model adapter to override condition hash matching in Rule, also clean up Mongoid adapter and specs
This commit is contained in:
		
							parent
							
								
									685e926d96
								
							
						
					
					
						commit
						cef6c21232
					
				@ -15,6 +15,17 @@ module CanCan
 | 
			
		||||
        false # override in subclass
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Used to determine if this model adapter will override the matching behavior for a hash of conditions.
 | 
			
		||||
      # If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
 | 
			
		||||
      def self.override_conditions_hash_matching?(subject, conditions)
 | 
			
		||||
        false
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Override if override_conditions_hash_matching? returns true
 | 
			
		||||
      def self.matches_conditions_hash?(subject, conditions)
 | 
			
		||||
        raise NotImplemented, "This model adapter does not support matching on a conditions hash."
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def initialize(model_class, rules)
 | 
			
		||||
        @model_class = model_class
 | 
			
		||||
        @rules = rules
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,14 @@ module CanCan
 | 
			
		||||
        model_class <= Mongoid::Document
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.override_conditions_hash_matching?(subject, conditions)
 | 
			
		||||
        conditions.any? { |k,v| !k.kind_of?(Symbol) }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.matches_conditions_hash?(subject, conditions)
 | 
			
		||||
        subject.class.where(conditions).include?(subject)  # just use Mongoid's where function
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def database_records
 | 
			
		||||
        @model_class.where(conditions)
 | 
			
		||||
      end
 | 
			
		||||
@ -23,33 +31,6 @@ module CanCan
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # customize to handle Mongoid queries in ability definitions conditions
 | 
			
		||||
  # Mongoid Criteria are simpler to check than normal conditions hashes
 | 
			
		||||
  # 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 way ruby handles all? for empty hashes can be unexpected:
 | 
			
		||||
  #   {}.all?{|a| a == 5}
 | 
			
		||||
  #   => true
 | 
			
		||||
  #   {}.all?{|a| a != 5}
 | 
			
		||||
  #   => true
 | 
			
		||||
  class Rule
 | 
			
		||||
    def matches_conditions_hash_with_mongoid_subject?(subject, conditions = @conditions)
 | 
			
		||||
      if defined?(::Mongoid) && subject.class.include?(::Mongoid::Document) && conditions.any?{|k,v| !k.kind_of?(Symbol)}
 | 
			
		||||
        if conditions.empty?
 | 
			
		||||
          true
 | 
			
		||||
        else
 | 
			
		||||
          subject.class.where(conditions).include?(subject)  # just use Mongoid's where function
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        matches_conditions_hash_without_mongoid_subject? subject, conditions
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # could use alias_method_chain, but it's not worth adding activesupport as a gem dependency
 | 
			
		||||
    alias_method :matches_conditions_hash_without_mongoid_subject?, :matches_conditions_hash?
 | 
			
		||||
    alias_method :matches_conditions_hash?, :matches_conditions_hash_with_mongoid_subject?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# simplest way to add `accessible_by` to all Mongoid Documents
 | 
			
		||||
 | 
			
		||||
@ -88,7 +88,17 @@ module CanCan
 | 
			
		||||
      @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Checks if the given subject matches the given conditions hash.
 | 
			
		||||
    # This behavior can be overriden by a model adapter by defining two class methods:
 | 
			
		||||
    # override_matching_for_conditions?(subject, conditions) and
 | 
			
		||||
    # matches_conditions_hash?(subject, conditions)
 | 
			
		||||
    def matches_conditions_hash?(subject, conditions = @conditions)
 | 
			
		||||
      if conditions.empty?
 | 
			
		||||
        true
 | 
			
		||||
      else
 | 
			
		||||
        if model_adapter(subject).override_conditions_hash_matching? subject, conditions
 | 
			
		||||
          model_adapter(subject).matches_conditions_hash? subject, conditions
 | 
			
		||||
        else
 | 
			
		||||
          conditions.all? do |name, value|
 | 
			
		||||
            attribute = subject.send(name)
 | 
			
		||||
            if value.kind_of?(Hash)
 | 
			
		||||
@ -104,6 +114,8 @@ module CanCan
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def nested_subject_matches_conditions?(subject_hash)
 | 
			
		||||
      parent, child = subject_hash.shift
 | 
			
		||||
@ -117,5 +129,9 @@ module CanCan
 | 
			
		||||
        @block.call(action, subject.class, subject, *extra_args)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def model_adapter(subject)
 | 
			
		||||
      ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -18,31 +18,8 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe CanCan::ModelAdapters::MongoidAdapter do
 | 
			
		||||
    context "Mongoid not defined" do
 | 
			
		||||
      before(:all) do
 | 
			
		||||
        @mongoid_class = Object.send(:remove_const, :Mongoid)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      after(:all) do
 | 
			
		||||
        Object.const_set(:Mongoid, @mongoid_class)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should not raise an error for ActiveRecord models" do
 | 
			
		||||
        @model_class = Class.new(Project)
 | 
			
		||||
        stub(@model_class).scoped { :scoped_stub }
 | 
			
		||||
        @ability = Object.new
 | 
			
		||||
        @ability.extend(CanCan::Ability)
 | 
			
		||||
 | 
			
		||||
        @ability.can :read, @model_class
 | 
			
		||||
        lambda {
 | 
			
		||||
          @ability.can? :read, @model_class.new
 | 
			
		||||
        }.should_not raise_error
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "Mongoid defined" do
 | 
			
		||||
      before(:each) do
 | 
			
		||||
        @model_class = MongoidProject
 | 
			
		||||
        @ability = Object.new
 | 
			
		||||
        @ability.extend(CanCan::Ability)
 | 
			
		||||
      end
 | 
			
		||||
@ -55,121 +32,123 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
 | 
			
		||||
 | 
			
		||||
      it "should be for only Mongoid classes" do
 | 
			
		||||
        CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
 | 
			
		||||
        CanCan::ModelAdapters::MongoidAdapter.should be_for_class(@model_class)
 | 
			
		||||
        CanCan::ModelAdapters::AbstractAdapter.adapter_class(@model_class).should == CanCan::ModelAdapters::MongoidAdapter
 | 
			
		||||
        CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
 | 
			
		||||
        CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should compare properties on mongoid documents with the conditions hash" do
 | 
			
		||||
        model = @model_class.new
 | 
			
		||||
        @ability.can :read, @model_class, :id => model.id
 | 
			
		||||
        @ability.should be_able_to :read, model
 | 
			
		||||
        model = MongoidProject.new
 | 
			
		||||
        @ability.can :read, MongoidProject, :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
 | 
			
		||||
        @model_class.create :title  => 'Sir'
 | 
			
		||||
        @model_class.create :title  => 'Lord'
 | 
			
		||||
        @model_class.create :title  => 'Dude'
 | 
			
		||||
        MongoidProject.create(:title => 'Sir')
 | 
			
		||||
        MongoidProject.create(:title => 'Lord')
 | 
			
		||||
        MongoidProject.create(:title => 'Dude')
 | 
			
		||||
 | 
			
		||||
        @model_class.accessible_by(@ability, :read).entries.should == []
 | 
			
		||||
        MongoidProject.accessible_by(@ability, :read).entries.should == []
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should return the correct records based on the defined ability" do
 | 
			
		||||
        @ability.can :read, @model_class, :title => "Sir"
 | 
			
		||||
        sir   = @model_class.create :title  => 'Sir'
 | 
			
		||||
        lord  = @model_class.create :title  => 'Lord'
 | 
			
		||||
        dude  = @model_class.create :title  => 'Dude'
 | 
			
		||||
        @ability.can :read, MongoidProject, :title => "Sir"
 | 
			
		||||
        sir   = MongoidProject.create(:title => 'Sir')
 | 
			
		||||
        lord  = MongoidProject.create(:title => 'Lord')
 | 
			
		||||
        dude  = MongoidProject.create(:title => 'Dude')
 | 
			
		||||
 | 
			
		||||
        @model_class.accessible_by(@ability, :read).should == [sir]
 | 
			
		||||
        MongoidProject.accessible_by(@ability, :read).should == [sir]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should return everything when the defined ability is manage all" do
 | 
			
		||||
        @ability.can :manage, :all
 | 
			
		||||
        sir   = @model_class.create :title  => 'Sir'
 | 
			
		||||
        lord  = @model_class.create :title  => 'Lord'
 | 
			
		||||
        dude  = @model_class.create :title  => 'Dude'
 | 
			
		||||
        sir   = MongoidProject.create(:title => 'Sir')
 | 
			
		||||
        lord  = MongoidProject.create(:title => 'Lord')
 | 
			
		||||
        dude  = MongoidProject.create(:title => 'Dude')
 | 
			
		||||
 | 
			
		||||
        @model_class.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
 | 
			
		||||
        MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
 | 
			
		||||
        it "should handle :field.in" do
 | 
			
		||||
          obj = @model_class.create :title  => 'Sir'
 | 
			
		||||
          @ability.can :read, @model_class, :title.in => ["Sir", "Madam"]
 | 
			
		||||
          obj = MongoidProject.create(:title => 'Sir')
 | 
			
		||||
          @ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
 | 
			
		||||
          @ability.can?(:read, obj).should == true
 | 
			
		||||
          @model_class.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
          MongoidProject.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
 | 
			
		||||
          obj2 = @model_class.create :title  => 'Lord'
 | 
			
		||||
          obj2 = MongoidProject.create(:title => 'Lord')
 | 
			
		||||
          @ability.can?(:read, obj2).should == false
 | 
			
		||||
        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'
 | 
			
		||||
            obj = MongoidProject.create(:title => 'Bird')
 | 
			
		||||
            @conditions = {:title.nin => ["Fork", "Spoon"]}
 | 
			
		||||
            mock(@model_class).where(@conditions) {[obj]}
 | 
			
		||||
            @ability.can :read, @model_class, @conditions
 | 
			
		||||
            mock(MongoidProject).where(@conditions) {[obj]}
 | 
			
		||||
            @ability.can :read, MongoidProject, @conditions
 | 
			
		||||
            @ability.should be_able_to(:read, obj)
 | 
			
		||||
          end
 | 
			
		||||
          it "Calls the base version if there are no mongoid criteria" do
 | 
			
		||||
            obj = @model_class.new :title  => 'Bird'
 | 
			
		||||
            obj = MongoidProject.new(:title => 'Bird')
 | 
			
		||||
            @conditions = {:id => obj.id}
 | 
			
		||||
            @ability.can :read, @model_class, @conditions
 | 
			
		||||
            @ability.can :read, MongoidProject, @conditions
 | 
			
		||||
            @ability.should be_able_to(:read, obj)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "should handle :field.nin" do
 | 
			
		||||
          obj = @model_class.create :title  => 'Sir'
 | 
			
		||||
          @ability.can :read, @model_class, :title.nin => ["Lord", "Madam"]
 | 
			
		||||
          obj = MongoidProject.create(:title => 'Sir')
 | 
			
		||||
          @ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"]
 | 
			
		||||
          @ability.can?(:read, obj).should == true
 | 
			
		||||
          @model_class.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
          MongoidProject.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
 | 
			
		||||
          obj2 = @model_class.create :title  => 'Lord'
 | 
			
		||||
          obj2 = MongoidProject.create(:title => 'Lord')
 | 
			
		||||
          @ability.can?(:read, obj2).should == false
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "should handle :field.size" do
 | 
			
		||||
          obj = @model_class.create :titles  => ['Palatin', 'Margrave']
 | 
			
		||||
          @ability.can :read, @model_class, :titles.size => 2
 | 
			
		||||
          obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
 | 
			
		||||
          @ability.can :read, MongoidProject, :titles.size => 2
 | 
			
		||||
          @ability.can?(:read, obj).should == true
 | 
			
		||||
          @model_class.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
          MongoidProject.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
 | 
			
		||||
          obj2 = @model_class.create :titles  => ['Palatin', 'Margrave', 'Marquis']
 | 
			
		||||
          obj2 = MongoidProject.create(:titles => ['Palatin', 'Margrave', 'Marquis'])
 | 
			
		||||
          @ability.can?(:read, obj2).should == false
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "should handle :field.exists" do
 | 
			
		||||
          obj = @model_class.create :titles  => ['Palatin', 'Margrave']
 | 
			
		||||
          @ability.can :read, @model_class, :titles.exists => true
 | 
			
		||||
          obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
 | 
			
		||||
          @ability.can :read, MongoidProject, :titles.exists => true
 | 
			
		||||
          @ability.can?(:read, obj).should == true
 | 
			
		||||
          @model_class.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
          MongoidProject.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
 | 
			
		||||
          obj2 = @model_class.create
 | 
			
		||||
          obj2 = MongoidProject.create
 | 
			
		||||
          @ability.can?(:read, obj2).should == false
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "should handle :field.gt" do
 | 
			
		||||
          obj = @model_class.create :age  => 50
 | 
			
		||||
          @ability.can :read, @model_class, :age.gt => 45
 | 
			
		||||
          obj = MongoidProject.create(:age => 50)
 | 
			
		||||
          @ability.can :read, MongoidProject, :age.gt => 45
 | 
			
		||||
          @ability.can?(:read, obj).should == true
 | 
			
		||||
          @model_class.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
          MongoidProject.accessible_by(@ability, :read).should == [obj]
 | 
			
		||||
 | 
			
		||||
          obj2 = @model_class.create :age  => 40
 | 
			
		||||
          obj2 = MongoidProject.create(:age => 40)
 | 
			
		||||
          @ability.can?(:read, obj2).should == false
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should call where with matching ability conditions" do
 | 
			
		||||
        obj = @model_class.create :foo => {:bar => 1}
 | 
			
		||||
        @ability.can :read, @model_class, :foo => {:bar => 1}
 | 
			
		||||
        @model_class.accessible_by(@ability, :read).entries.first.should == obj
 | 
			
		||||
        obj = MongoidProject.create(:foo => {:bar => 1})
 | 
			
		||||
        @ability.can :read, MongoidProject, :foo => {:bar => 1}
 | 
			
		||||
        MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "should not allow to fetch records when ability with just block present" do
 | 
			
		||||
        @ability.can :read, @model_class do false end
 | 
			
		||||
        @ability.can :read, MongoidProject do
 | 
			
		||||
          false
 | 
			
		||||
        end
 | 
			
		||||
        lambda {
 | 
			
		||||
          @model_class.accessible_by(@ability)
 | 
			
		||||
          MongoidProject.accessible_by(@ability)
 | 
			
		||||
        }.should raise_error(CanCan::Error)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user