module SimpleCancan::Ability

Public Instance Methods

alias_action(*args) click to toggle source

Alias one or more actions into another one.

alias_action :update, :destroy, :to => :modify
can :modify, Comment

Then :modify permission will apply to both :update and :destroy requests.

can? :update, Comment # => true
can? :destroy, Comment # => true

This only works in one direction. Passing the aliased action into the “can?” call will not work because aliases are meant to generate more generic actions.

alias_action :update, :destroy, :to => :modify
can :update, Comment
can? :modify, Comment # => false

Unless that exact alias is used.

can :modify, Comment
can? :modify, 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

This way one can use params in the controller to determine the permission.

# File lib/simple_cancan/ability.rb, line 122
def alias_action(*args)
  target = args.pop[:to]
  validate_target(target)
  aliased_actions[target] ||= []
  aliased_actions[target] += args
end
aliased_actions() click to toggle source

Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.

# File lib/simple_cancan/ability.rb, line 135
def aliased_actions
  @aliased_actions ||= default_alias_actions
end
attributes_for(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 170
def attributes_for(action, subject)
  attributes = {}
  relevant_rules(action, subject).map do |rule|
    attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
  end
  attributes
end
authorize!(action, subject, *args) click to toggle source

See ControllerAdditions#authorize! for documentation.

# File lib/simple_cancan/ability.rb, line 150
def authorize!(action, subject, *args)
  message = nil
  if args.last.kind_of?(Hash) && args.last.has_key?(:message)
    message = args.pop[:message]
  end
  if cannot?(action, subject, *args)
    message ||= unauthorized_message(action, subject)
    raise AccessDenied.new(message, action, subject)
  end
  subject
end
can(action = nil, subject = nil, conditions = nil, &block) click to toggle source

Defines which abilities are allowed using two arguments. The first one is the action you’re setting the permission for, the second one is the class of object you’re setting it on.

can :update, Article

You can pass an array for either of these parameters to match any one. Here the user has the ability to update or destroy both articles and comments.

can [:update, :destroy], [Article, Comment]

You can pass :all to match any object and :manage to match any action. Here are some examples.

can :manage, :all
can :update, :all
can :manage, Project

You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.

can :read, Project, :active => true, :user_id => user.id

See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions are also used for initial attributes when building a record in ControllerAdditions#load_resource.

If the conditions hash does not give you enough control over defining abilities, you can use a block along with any Ruby code you want.

can :update, Project do |project|
  project.groups.include?(user.group)
end

If the block returns true then the user has that :update ability for that project, otherwise he will be denied access. The downside to using a block is that it cannot be used to generate conditions for database queries.

You can pass custom objects into this “can” method, this is usually done with a symbol and is useful if a class isn’t available to define permissions on.

can :read, :stats
can? :read, :stats # => true

IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.

can :update, Project, :priority => 3
can? :update, Project # => true

If you pass no arguments to can, the action, class, and object will be passed to the block and the block will always be executed. This allows you to override the full behavior if the permissions are defined in an external source such as the database.

can do |action, object_class, object|
  # check the database and return true/false
end
# File lib/simple_cancan/ability.rb, line 73
def can(action = nil, subject = nil, conditions = nil, &block)
  rules << Rule.new(true, action, subject, conditions, block)
end
can?(action, subject, *extra_args) click to toggle source
# File lib/simple_cancan/ability.rb, line 5
def can?(action, subject, *extra_args)
  match = relevant_rules_for_match(action, subject).detect do |rule|
    rule.matches_conditions?(action, subject, extra_args)
  end
  match ? match.base_behavior : false
end
cannot(action = nil, subject = nil, conditions = nil, &block) click to toggle source

Defines 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
# File lib/simple_cancan/ability.rb, line 89
def cannot(action = nil, subject = nil, conditions = nil, &block)
  rules << Rule.new(false, action, subject, conditions, block)
end
cannot?(*args) click to toggle source

Convenience method which works the same as “can?” but returns the opposite value.

cannot? :destroy, @project
# File lib/simple_cancan/ability.rb, line 16
def cannot?(*args)
  !can?(*args)
end
clear_aliased_actions() click to toggle source

Removes previously aliased actions including the defaults.

# File lib/simple_cancan/ability.rb, line 140
def clear_aliased_actions
  @aliased_actions = {}
end
has_block?(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 178
def has_block?(action, subject)
  relevant_rules(action, subject).any?(&:only_block?)
end
has_raw_sql?(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 182
def has_raw_sql?(action, subject)
  relevant_rules(action, subject).any?(&:only_raw_sql?)
end
merge(ability) click to toggle source
# File lib/simple_cancan/ability.rb, line 186
def merge(ability)
  ability.send(:rules).each do |rule|
    rules << rule.dup
  end
  self
end
model_adapter(model_class, action) click to toggle source
# File lib/simple_cancan/ability.rb, line 144
def model_adapter(model_class, action)
  adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
  adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
end
unauthorized_message(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 162
def unauthorized_message(action, subject)
  keys = unauthorized_message_keys(action, subject)
  variables = {:action => action.to_s}
  variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
  message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
  message.blank? ? nil : message
end
validate_target(target) click to toggle source

User shouldn’t specify targets with names of real actions or it will cause Seg fault

# File lib/simple_cancan/ability.rb, line 130
def validate_target(target)
  raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
end

Private Instance Methods

aliases_for_action(action) click to toggle source

Given an action, it will try to find all of the actions which are aliased to it. This does the opposite kind of lookup as expand_actions.

# File lib/simple_cancan/ability.rb, line 215
def aliases_for_action(action)
  results = [action]
  aliased_actions.each do |aliased_action, actions|
    results += aliases_for_action(aliased_action) if actions.include? action
  end
  results
end
default_alias_actions() click to toggle source
# File lib/simple_cancan/ability.rb, line 252
def default_alias_actions
  {
    :read => [:index, :show],
    :create => [:new],
    :update => [:edit],
  }
end
expand_actions(actions) click to toggle source

Accepts an array of actions and returns an array of actions which match. This should be called before “matches?” and other checking methods since they rely on the actions to be expanded.

# File lib/simple_cancan/ability.rb, line 207
def expand_actions(actions)
  actions.map do |action|
    aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
  end.flatten
end
relevant_rules(action, subject) click to toggle source

Returns an array of Rule instances which match the action and subject This does not take into consideration any hash conditions or block statements

# File lib/simple_cancan/ability.rb, line 229
def relevant_rules(action, subject)
  rules.reverse.select do |rule|
    rule.expanded_actions = expand_actions(rule.actions)
    rule.relevant? action, subject
  end
end
relevant_rules_for_match(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 236
def relevant_rules_for_match(action, subject)
  relevant_rules(action, subject).each do |rule|
    if rule.only_raw_sql?
      raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
    end
  end
end
relevant_rules_for_query(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 244
def relevant_rules_for_query(action, subject)
  relevant_rules(action, subject).each do |rule|
    if rule.only_block?
      raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
    end
  end
end
rules() click to toggle source
# File lib/simple_cancan/ability.rb, line 223
def rules
  @rules ||= []
end
unauthorized_message_keys(action, subject) click to toggle source
# File lib/simple_cancan/ability.rb, line 195
def unauthorized_message_keys(action, subject)
  subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
  [subject, :all].map do |try_subject|
    [aliases_for_action(action), :manage].flatten.map do |try_action|
      :"#{try_action}.#{try_subject}"
    end
  end.flatten
end