Module: CanCan::Ability

Included in:
Ability
Defined in:
lib/cancan/ability.rb

Overview

This module is designed to be included into an Ability class. This will provide the “can” methods for defining and checking abilities.

class Ability
  include CanCan::Ability

  def initialize(user)
    if user.admin?
      can :manage, :all
    else
      can :read, :all
    end
  end
end

Instance Method Summary collapse

Instance Method Details

#alias_action(*args) ⇒ Object

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.



173
174
175
176
177
# File 'lib/cancan/ability.rb', line 173

def alias_action(*args)
  target = args.pop[:to]
  aliased_actions[target] ||= []
  aliased_actions[target] += args
end

#aliased_actionsObject

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



180
181
182
# File 'lib/cancan/ability.rb', line 180

def aliased_actions
  @aliased_actions ||= default_alias_actions
end

#attributes_for(action, subject) ⇒ Object



215
216
217
218
219
220
221
# File 'lib/cancan/ability.rb', line 215

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) ⇒ Object

See ControllerAdditions#authorize! for documentation.



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/cancan/ability.rb', line 195

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) ⇒ Object

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


124
125
126
# File 'lib/cancan/ability.rb', line 124

def can(action = nil, subject = nil, conditions = nil, &block)
  rules << Rule.new(true, action, subject, conditions, block)
end

#can?(action, subject, *extra_args) ⇒ Boolean

Check if the user has permission to perform a given action on an object.

can? :destroy, @project

You can also pass the class instead of an instance (if you don’t have one handy).

can? :create, Project

Nested resources can be passed through a hash, this way conditions which are dependent upon the association will work when using a class.

can? :create, @category => Project

Any additional arguments will be passed into the “can” block definition. This can be used to pass more information about the user’s request for example.

can? :create, Project, request.remote_ip

can :create Project do |project, remote_ip|
  # ...
end

Not only can you use the can? method in the controller and view (see ControllerAdditions), but you can also call it directly on an ability instance.

ability.can? :destroy, @project

This makes testing a user’s abilities very easy.

def test "user can only destroy projects which he owns"
  user = User.new
  ability = Ability.new(user)
  assert ability.can?(:destroy, Project.new(:user => user))
  assert ability.cannot?(:destroy, Project.new)
end

Also see the RSpec Matchers to aid in testing.

Returns:

  • (Boolean)


56
57
58
59
60
61
# File 'lib/cancan/ability.rb', line 56

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) ⇒ Object

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


140
141
142
# File 'lib/cancan/ability.rb', line 140

def cannot(action = nil, subject = nil, conditions = nil, &block)
  rules << Rule.new(false, action, subject, conditions, block)
end

#cannot?(*args) ⇒ Boolean

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

cannot? :destroy, @project

Returns:

  • (Boolean)


67
68
69
# File 'lib/cancan/ability.rb', line 67

def cannot?(*args)
  !can?(*args)
end

#clear_aliased_actionsObject

Removes previously aliased actions including the defaults.



185
186
187
# File 'lib/cancan/ability.rb', line 185

def clear_aliased_actions
  @aliased_actions = {}
end

#has_block?(action, subject) ⇒ Boolean

Returns:

  • (Boolean)


223
224
225
# File 'lib/cancan/ability.rb', line 223

def has_block?(action, subject)
  relevant_rules(action, subject).any?(&:only_block?)
end

#has_raw_sql?(action, subject) ⇒ Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/cancan/ability.rb', line 227

def has_raw_sql?(action, subject)
  relevant_rules(action, subject).any?(&:only_raw_sql?)
end

#merge(ability) ⇒ Object



231
232
233
234
235
236
# File 'lib/cancan/ability.rb', line 231

def merge(ability)
  ability.send(:rules).each do |rule|
    rules << rule.dup
  end
  self
end

#model_adapter(model_class, action) ⇒ Object



189
190
191
192
# File 'lib/cancan/ability.rb', line 189

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) ⇒ Object



207
208
209
210
211
212
213
# File 'lib/cancan/ability.rb', line 207

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