Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

3 participants

@archan937

Currently, it isn't possible to Marshal.dump ability instances with "procced abilities".

class Foo
  include CanCan::Ability
end

foo = Foo.new
foo.can :read, String, nil do |instance|
  # do something
end

Marshal.dump foo
=> TypeError: no marshal_dump is defined for class Proc

So instead of providing a proc, I have made it possible to specify a method of the ability instance used for checking abilities.

class Foo
  include CanCan::Ability
  def evaluate(instance, conditions)
    # do something
  end
end

foo = Foo.new
foo.can :read, String, nil, :evaluate

Marshal.dump foo
=> "\x04\bo:\bFoo\x06:\v@rules[\x06o:\x11CanCan::Rule\f:\x0F@match_allF:\x13@base_behaviorT:\r@actions[\x06:\tread:\x0E@subjects[\x06c\vString:\x10@conditions{\x00:\f@method:\revaluate:\r@ability@\x00"
Paul Engel Being able to accept a method name of the ability used for checking a…
…bilities

Doing this provides us Marshal dumping abilities with "proc ability checking"
7a966d4
@andhapp
Collaborator

@archan937: What's the reason to Marshal the ability class? I've never had to do it so curious to know.

@archan937

We are storing our abilities in Memcache. Except "procced abilities" aren't possible to dump.

@xhoy

Dear submitter, Since cancan/raynB hasn't been active for more than 6 months and no body else then ryam himself has commit permissions the cancan project is on a stand still.
Since cancan has several issues including missing support for rails 4 cancan is moving forward to cancancan. More details on: #994

If your feel that your pull request or bug is still applicable (and hasn't been merged in to cancan) it would be really appreciated if you would resubmit it to cancancan (https://github.com/cancancommunity/cancancan)

We hope to see you on the other side!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 27, 2012
  1. Being able to accept a method name of the ability used for checking a…

    Paul Engel authored
    …bilities
    
    Doing this provides us Marshal dumping abilities with "proc ability checking"
Commits on Jul 30, 2012
  1. Being able to pass conditions to a mapped method

    Paul Engel authored
This page is out of date. Refresh to see the latest.
View
14 lib/cancan/ability.rb
@@ -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
28 lib/cancan/rule.rb
@@ -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
View
49 spec/cancan/ability_spec.rb
@@ -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
10 spec/cancan/rule_spec.rb
@@ -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
Something went wrong with that request. Please try again.