Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Being able to specify a method name (of the ability) used for checking abilities (instead of procs) #702

Open
wants to merge 2 commits into
from
Jump to file or symbol
Failed to load files and symbols.
+91 −10
Split
View
@@ -121,8 +121,11 @@ def cannot?(*args)
# # check the database and return true/false
# end
#
- def can(action = nil, subject = nil, conditions = nil, &block)
- rules << Rule.new(true, action, subject, conditions, block)
+ def can(action = nil, subject = nil, conditions = nil, method = nil, &block)
+ if method && block_given?
+ raise ArgumentError, "You cannot pass both a block and a mapped method when defining an ability"
+ end
+ rules << Rule.new(true, action, subject, conditions, method || block, self)
end
# Defines an ability which cannot be done. Accepts the same arguments as "can".
@@ -137,8 +140,11 @@ def can(action = nil, subject = nil, conditions = nil, &block)
# product.invisible?
# end
#
- def cannot(action = nil, subject = nil, conditions = nil, &block)
- rules << Rule.new(false, action, subject, conditions, block)
+ def cannot(action = nil, subject = nil, conditions = nil, method = nil, &block)
+ if method && block_given?
+ raise ArgumentError, "You cannot pass both a block and a mapped method when defining an inability"
+ end
+ rules << Rule.new(false, action, subject, conditions, method || block, self)
end
# Alias one or more actions into another one.
View
@@ -10,14 +10,16 @@ class Rule # :nodoc:
# value. True for "can" and false for "cannot". The next two arguments are the action
# and subject respectively (such as :read, @project). The third argument is a hash
# of conditions and the last one is the block passed to the "can" call.
- def initialize(base_behavior, action, subject, conditions, block)
- raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
+ def initialize(base_behavior, action, subject, conditions, block_or_method, ability = nil)
+ raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && block_or_method.kind_of?(Proc)
@match_all = action.nil? && subject.nil?
@base_behavior = base_behavior
@actions = [action].flatten
@subjects = [subject].flatten
@conditions = conditions || {}
- @block = block
+ @block = block_or_method if block_or_method.kind_of?(Proc)
+ @method = block_or_method unless block_or_method.kind_of?(Proc)
+ @ability = ability
end
# Matches both the subject and action, not necessarily the conditions
@@ -29,9 +31,15 @@ def relevant?(action, subject)
# Matches the block or conditions hash
def matches_conditions?(action, subject, extra_args)
if @match_all
- call_block_with_all(action, subject, extra_args)
+ if @block
+ call_block_with_all(action, subject, extra_args)
+ else
+ call_method_with_all(action, subject, extra_args)
+ end
elsif @block && !subject_class?(subject)
@block.call(subject, *extra_args)
+ elsif @method && !subject_class?(subject)
+ @ability.send(@method, subject, @conditions, *extra_args)
elsif @conditions.kind_of?(Hash) && subject.class == Hash
nested_subject_matches_conditions?(subject)
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
@@ -43,11 +51,11 @@ def matches_conditions?(action, subject, extra_args)
end
def only_block?
- conditions_empty? && !@block.nil?
+ conditions_empty? && !(@block || @method).nil?
end
def only_raw_sql?
- @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
+ (@block || @method).nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
end
def conditions_empty?
@@ -139,6 +147,14 @@ def call_block_with_all(action, subject, extra_args)
end
end
+ def call_method_with_all(action, subject, extra_args)
+ if subject.class == Class
+ @ability.send(@method, action, subject, nil, @conditions, *extra_args)
+ else
+ @ability.send(@method, action, subject.class, subject, @conditions, *extra_args)
+ end
+ end
+
def model_adapter(subject)
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
end
@@ -449,4 +449,53 @@ class Container < Hash; end
@ability.send(:rules).size.should == 2
end
end
+
+ describe "method mapping" do
+ class Foo
+ include CanCan::Ability
+ def evaluate(instance, conditions)
+ instance == "A"
+ end
+ end
+
+ before do
+ @ability = Foo.new
+ end
+
+ it "should be able to accept the name of a mapped method when defining an ability" do
+ @ability.can :read, String, nil, :evaluate
+ @ability.send(:rules).last.instance_variables.should include "@method"
+ end
+
+ it "should be able to accept the name of a mapped method when defining an inability" do
+ @ability.cannot :read, String, nil, :evaluate
+ @ability.send(:rules).last.instance_variables.should include "@method"
+ end
+
+ it "should raise an exception when passing both a block and a mapped method when defining an ability" do
+ block = proc{}
+ error_message = "You cannot pass both a block and a mapped method when defining an ability"
+ lambda { @ability.can :read, String, nil, :evaluate, &block }.should raise_error(ArgumentError, error_message)
+ end
+
+ it "should raise an exception when passing both a block and a mapped method when defining an inability" do
+ block = proc{}
+ error_message = "You cannot pass both a block and a mapped method when defining an inability"
+ lambda { @ability.cannot :read, String, nil, :evaluate, &block }.should raise_error(ArgumentError, error_message)
+ end
+
+ it "should invoke the mapped method when checking ability" do
+ @ability.can :read, String, nil, :evaluate
+ @ability.can?(:read, "A").should be true
+ @ability.can?(:read, "B").should be false
+ end
+
+ it "should be able to be dumped using Marshal.load" do
+ @ability.can :read, String, nil, :evaluate
+ lambda { Marshal.dump @ability }.should_not raise_error
+
+ @ability.can :read, String, nil do |instance| instance == "A" end
+ lambda { Marshal.dump @ability }.should raise_error(TypeError)
+ end
+ end
end
View
@@ -44,4 +44,14 @@
@rule.should be_unmergeable
end
+
+ it "should be able to accept the name of a mapped method" do
+ rule = CanCan::Rule.new(true, :read, Integer, nil, :evaluate)
+ rule.instance_variable_get(:@block).should be nil
+ rule.instance_variable_get(:@method).should be :evaluate
+
+ rule = CanCan::Rule.new(true, :read, Integer, nil, proc{})
+ rule.instance_variable_get(:@block).should_not be nil
+ rule.instance_variable_get(:@method).should be nil
+ end
end