Permalink
Browse files

allow model adapter to override condition hash matching in Rule, also…

… clean up Mongoid adapter and specs
  • Loading branch information...
1 parent 685e926 commit cef6c21232aafdfd43c188c00fc0b4f3f274288a @ryanb committed Jan 4, 2011
@@ -15,6 +15,17 @@ def self.for_class?(member_class)
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 @@ def self.for_class?(model_class)
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 @@ def false_query
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
View
@@ -88,19 +88,31 @@ def matches_subject_class?(subject)
@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)
- conditions.all? do |name, value|
- 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
+ if conditions.empty?
+ true
+ else
+ if model_adapter(subject).override_conditions_hash_matching? subject, conditions
+ model_adapter(subject).matches_conditions_hash? subject, conditions
else
- attribute == value
+ conditions.all? do |name, value|
+ 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
@@ -117,5 +129,9 @@ def call_block_with_all(action, subject, extra_args)
@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 @@ class MongoidProject
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 @@ class MongoidProject
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

0 comments on commit cef6c21

Please sign in to comment.