diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index efeb9f0..91a2a20 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,3 +1,5 @@ +* adding "cannot" method to define which abilities cannot be done - see issue #7 + * support custom objects (usually symbols) in can definition - see issue #8 0.2.0 (Nov 17, 2009) diff --git a/README.rdoc b/README.rdoc index e88fd02..f86b7b4 100644 --- a/README.rdoc +++ b/README.rdoc @@ -111,17 +111,11 @@ You can also pass :manage as the action which will match any action. In this cas can :manage, Comment do |action, comment| action != :destroy end + +Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done. -Finally, you can use the "alias_action" method to alias one or more actions into one. - - alias_action :update, :destroy, :to => :modify - can :modify, Comment - -The following aliases are added by default for conveniently mapping common controller actions. - - alias_action :index, :show, :to => :read - alias_action :new, :to => :create - alias_action :edit, :to => :update + can :read, :all + cannot :read, Product == Checking Abilities @@ -141,6 +135,21 @@ The "cannot?" method is for convenience and performs the opposite check of "can? cannot? :destroy, @project +== Aliasing Actions + +You can use the "alias_action" method to alias one or more actions into one. + + alias_action :update, :destroy, :to => :modify + can :modify, Comment + can? :update, Comment # => true + +The following aliases are added by default for conveniently mapping common controller actions. + + alias_action :index, :show, :to => :read + alias_action :new, :to => :create + alias_action :edit, :to => :update + + == Assumptions & Configuring CanCan makes two assumptions about your application. diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index e56b04c..de1cb86 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -41,19 +41,19 @@ module CanCan # end # def can?(original_action, target) # TODO this could use some refactoring - (@can_history || []).reverse.each do |can_action, can_target, can_block| + (@can_history || []).reverse.each do |base_behavior, can_action, can_target, can_block| can_actions = [can_action].flatten can_targets = [can_target].flatten possible_actions_for(original_action).each do |action| if (can_actions.include?(:manage) || can_actions.include?(action)) && (can_targets.include?(:all) || can_targets.include?(target) || can_targets.any? { |c| c.kind_of?(Class) && target.kind_of?(c) }) if can_block.nil? - return true + return base_behavior else block_args = [] block_args << action if can_actions.include?(:manage) block_args << (target.class == Class ? target : target.class) if can_targets.include?(:all) block_args << (target.class == Class ? nil : target) - return can_block.call(*block_args) + return base_behavior ? can_block.call(*block_args) : !can_block.call(*block_args) end end end @@ -114,7 +114,24 @@ module CanCan # def can(action, target, &block) @can_history ||= [] - @can_history << [action, target, block] + @can_history << [true, action, target, block] + end + + # Define an ability which cannot be done. Accepts the same arguments as "can". + # + # can :read, :all + # cannot :read, Comment + # + # A block can be passed just like "can", however if the logic is complex it is recommended + # to use the "can" method. + # + # cannot :read, Product do |product| + # product.invisible? + # end + # + def cannot(action, target, &block) + @can_history ||= [] + @can_history << [false, action, target, block] end # Alias one or more actions into another one. diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 5890a12..3377e97 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -106,4 +106,21 @@ describe CanCan::Ability do @ability.can?(:update, :stats).should be_false @ability.can?(:read, :nonstats).should be_false end + + it "should support 'cannot' method to define what user cannot do" do + @ability.can :read, :all + @ability.cannot :read, Integer + @ability.can?(:read, "foo").should be_true + @ability.can?(:read, 123).should be_false + end + + it "should support block on 'cannot' method" do + @ability.can :read, :all + @ability.cannot :read, Integer do |int| + int > 5 + end + @ability.can?(:read, "foo").should be_true + @ability.can?(:read, 3).should be_true + @ability.can?(:read, 123).should be_false + end end