passing throw matching rules with not matching conditions
Main goal is to allow: cannot :manage, :all can :read, :all can :manage, User, :id=>user.id can :manage, User, :manager_id=>user.id Signed-off-by: Sokolov Yura <funny.falcon@gmail.com>
This commit is contained in:
@@ -49,8 +49,12 @@ module CanCan
|
||||
#
|
||||
def can?(action, subject, *extra_args)
|
||||
raise Error, "Nom nom nom. I eated it." if action == :has && subject == :cheezburger
|
||||
can_definition = matching_can_definition(action, subject)
|
||||
can_definition && can_definition.can?(action, subject, extra_args)
|
||||
matching_can_definition(action, subject) do |can_definition|
|
||||
unless (can = can_definition.can?(action, subject, extra_args)) == :_NOT_MATCHED
|
||||
return can
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Convenience method which works the same as "can?" but returns the opposite value.
|
||||
@@ -179,11 +183,15 @@ module CanCan
|
||||
@aliased_actions = {}
|
||||
end
|
||||
|
||||
# Returns a hash of conditions which match the given ability. This is useful if you need to generate a database
|
||||
# query based on the current ability.
|
||||
# Returns an array of arrays composing from desired action and hash of conditions which match the given ability.
|
||||
# This is useful if you need to generate a database query based on the current ability.
|
||||
#
|
||||
# can :read, Article, :visible => true
|
||||
# conditions :read, Article # returns { :visible => true }
|
||||
# conditions :read, Article # returns [ [ true, { :visible => true } ] ]
|
||||
#
|
||||
# can :read, Article, :visible => true
|
||||
# cannot :read, Article, :blocked => true
|
||||
# conditions :read, Article # returns [ [ false, { :blocked => true } ], [ true, { :visible => true } ] ]
|
||||
#
|
||||
# Normally you will not call this method directly, but instead go through ActiveRecordAdditions#accessible_by method.
|
||||
#
|
||||
@@ -191,25 +199,74 @@ module CanCan
|
||||
# If the ability is defined using a block then this will raise an exception since a hash of conditions cannot be
|
||||
# determined from that.
|
||||
def conditions(action, subject)
|
||||
can_definition = matching_can_definition(action, subject)
|
||||
if can_definition
|
||||
raise Error, "Cannot determine ability conditions from block for #{action.inspect} #{subject.inspect}" if can_definition.block
|
||||
can_definition.conditions || {}
|
||||
matched = matching_can_definition(action, subject)
|
||||
unless matched.empty?
|
||||
if matched.any?{|can_definition| can_definition.conditions.nil? && can_definition.block }
|
||||
raise Error, "Cannot determine ability conditions from block for #{action.inspect} #{subject.inspect}"
|
||||
end
|
||||
matched.map{|can_definition|
|
||||
[can_definition.base_behavior, can_definition.conditions]
|
||||
}
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Returns sql conditions for object, which responds to :sanitize_sql .
|
||||
# This is useful if you need to generate a database query based on the current ability.
|
||||
#
|
||||
# can :manage, User, :id => 1
|
||||
# can :manage, User, :manager_id => 1
|
||||
# cannot :manage, User, :self_managed => true
|
||||
# sql_conditions :manage, User # returns not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))
|
||||
#
|
||||
# Normally you will not call this method directly, but instead go through ActiveRecordAdditions#accessible_by method.
|
||||
#
|
||||
# If the ability is not defined then false is returned so be sure to take that into consideration.
|
||||
# If there is just one :can ability, it conditions returned untouched.
|
||||
# If the ability is defined using a block then this will raise an exception since a hash of conditions cannot be
|
||||
# determined from that.
|
||||
def sql_conditions(action, subject)
|
||||
conds = conditions(action, subject)
|
||||
return false if conds == false
|
||||
return (conds[0][1] || {}) if conds.size==1 && conds[0][0] == true # to match previous spec
|
||||
|
||||
true_cond = subject.send(:sanitize_sql, ['?=?', true, true])
|
||||
false_cond = subject.send(:sanitize_sql, ['?=?', true, false])
|
||||
conds.reverse.inject(nil) do |sql, action|
|
||||
behavior, condition = action
|
||||
if condition
|
||||
condition = "#{subject.send(:sanitize_sql, condition)}"
|
||||
condition = "not (#{condition})" if behavior == false
|
||||
else
|
||||
condition = behavior ? true_cond : false_cond
|
||||
end
|
||||
case sql
|
||||
when nil then condition
|
||||
when true_cond
|
||||
behavior ? true_cond : condition
|
||||
when false_cond
|
||||
behavior ? condition : false_cond
|
||||
else
|
||||
behavior ? "(#{condition}) OR (#{sql})" : "#{condition} AND (#{sql})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the associations used in conditions. This is usually used in the :joins option for a search.
|
||||
# See ActiveRecordAdditions#accessible_by for use in Active Record.
|
||||
def association_joins(action, subject)
|
||||
can_definition = matching_can_definition(action, subject)
|
||||
if can_definition
|
||||
raise Error, "Cannot determine association joins from block for #{action.inspect} #{subject.inspect}" if can_definition.block
|
||||
can_definition.association_joins
|
||||
can_definitions = matching_can_definition(action, subject)
|
||||
unless can_definitions.empty?
|
||||
if can_definitions.any?{|can_definition| can_definition.conditions.nil? && can_definition.block }
|
||||
raise Error, "Cannot determine association joins from block for #{action.inspect} #{subject.inspect}"
|
||||
end
|
||||
collect_association_joins(can_definitions)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def can_definitions
|
||||
@@ -217,9 +274,18 @@ module CanCan
|
||||
end
|
||||
|
||||
def matching_can_definition(action, subject)
|
||||
can_definitions.reverse.detect do |can_definition|
|
||||
can_definition.expand_actions(aliased_actions)
|
||||
can_definition.matches? action, subject
|
||||
if block_given?
|
||||
can_definitions.reverse.each do |can_definition|
|
||||
can_definition.expand_actions(aliased_actions)
|
||||
if can_definition.matches? action, subject
|
||||
yield can_definition
|
||||
break if can_definition.conditions.nil? && can_definition.block.nil?
|
||||
end
|
||||
end
|
||||
else
|
||||
matched = []
|
||||
matching_can_definition(action, subject){|can_definition| matched << can_definition}
|
||||
matched
|
||||
end
|
||||
end
|
||||
|
||||
@@ -230,5 +296,32 @@ module CanCan
|
||||
:update => [:edit],
|
||||
}
|
||||
end
|
||||
|
||||
def collect_association_joins(can_definitions)
|
||||
joins = []
|
||||
can_definitions.each do |can_definition|
|
||||
merge_association_joins(joins, can_definition.association_joins || [])
|
||||
end
|
||||
clear_association_joins(joins)
|
||||
end
|
||||
|
||||
def merge_association_joins(what, with)
|
||||
with.each do |join|
|
||||
name, nested = join.each_pair.first
|
||||
if at = what.detect{|h| h.has_key?(name) }
|
||||
at[name] = merge_association_joins(at[name], nested)
|
||||
else
|
||||
what << join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def clear_association_joins(joins)
|
||||
joins.map do |join|
|
||||
name, nested = join.each_pair.first
|
||||
nested.empty? ? name : {name => clear_association_joins(nested)}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ module CanCan
|
||||
# Here only the articles which the user can update are returned. This
|
||||
# internally uses Ability#conditions method, see that for more information.
|
||||
def accessible_by(ability, action = :read)
|
||||
conditions = ability.conditions(action, self) || {:id => nil}
|
||||
conditions = ability.sql_conditions(action, self) || {:id => nil}
|
||||
joins = ability.association_joins(action, self)
|
||||
if respond_to? :where
|
||||
where(conditions).joins(joins)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module CanCan
|
||||
# This class is used internally and should only be called through Ability.
|
||||
class CanDefinition # :nodoc:
|
||||
attr_reader :conditions, :block
|
||||
attr_reader :conditions, :block, :base_behavior, :definitive
|
||||
|
||||
def initialize(base_behavior, action, subject, conditions, block)
|
||||
@base_behavior = base_behavior
|
||||
@@ -23,18 +23,19 @@ module CanCan
|
||||
|
||||
def can?(action, subject, extra_args)
|
||||
result = can_without_base_behavior?(action, subject, extra_args)
|
||||
return :_NOT_MATCHED if result == :_NOT_MATCHED || !result
|
||||
@base_behavior ? result : !result
|
||||
end
|
||||
|
||||
def association_joins(conditions = @conditions)
|
||||
joins = []
|
||||
conditions.each do |name, value|
|
||||
(conditions || []).each do |name, value|
|
||||
if value.kind_of? Hash
|
||||
nested = association_joins(value)
|
||||
if nested
|
||||
joins << {name => nested}
|
||||
else
|
||||
joins << name
|
||||
joins << {name => []}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -54,8 +55,10 @@ module CanCan
|
||||
def can_without_base_behavior?(action, subject, extra_args)
|
||||
if @block
|
||||
call_block(action, subject, extra_args)
|
||||
elsif @conditions && subject.class != Class
|
||||
matches_conditions? subject
|
||||
elsif @conditions.kind_of?(Hash) && subject.class != Class
|
||||
matches_conditions?(subject)
|
||||
elsif [true, false, :_NOT_MATCHES].include? @conditions
|
||||
@conditions
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user