Module: CanCan::Ability
- Includes:
- Actions, Rules, StrongParameterSupport, UnauthorizedMessageResolver
- Included in:
- Ability
- Defined in:
- lib/cancan/ability.rb,
lib/cancan/ability/rules.rb,
lib/cancan/ability/actions.rb,
lib/cancan/ability/strong_parameter_support.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
Defined Under Namespace
Modules: Actions, Rules, StrongParameterSupport
Instance Method Summary collapse
- #attributes_for(action, subject) ⇒ Object
-
#authorize!(action, subject, *args) ⇒ Object
See ControllerAdditions#authorize! for documentation.
-
#can(action = nil, subject = nil, *attributes_and_conditions, &block) ⇒ Object
Defines which abilities are allowed using two arguments.
-
#can?(action, subject, attribute = nil, *extra_args) ⇒ Boolean
Check if the user has permission to perform a given action on an object.
-
#cannot(action = nil, subject = nil, *attributes_and_conditions, &block) ⇒ Object
Defines an ability which cannot be done.
-
#cannot?(*args) ⇒ Boolean
Convenience method which works the same as “can?” but returns the opposite value.
- #extract_rule_in_permissions(permissions_list, rule) ⇒ Object
- #has_block?(action, subject) ⇒ Boolean
- #has_raw_sql?(action, subject) ⇒ Boolean
-
#merge(ability) ⇒ Object
Copies all rules and aliased actions of the given
CanCan::Ability
and adds them toself
. - #model_adapter(model_class, action) ⇒ Object
-
#permissions ⇒ Object
Return a hash of permissions for the user in the format of: { can: can_hash, cannot: cannot_hash }.
-
#validate_target(target) ⇒ Object
User shouldn’t specify targets with names of real actions or it will cause Seg fault.
Methods included from StrongParameterSupport
Methods included from UnauthorizedMessageResolver
#translate_subject, #unauthorized_message
Methods included from Actions
#alias_action, #aliased_actions, #clear_aliased_actions
Instance Method Details
#attributes_for(action, subject) ⇒ Object
185 186 187 188 189 190 191 |
# File 'lib/cancan/ability.rb', line 185 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.
176 177 178 179 180 181 182 183 |
# File 'lib/cancan/ability.rb', line 176 def (action, subject, *args) = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil if cannot?(action, subject, *args) ||= (action, subject) raise AccessDenied.new(, action, subject, args) end subject end |
#can(action = nil, subject = nil, *attributes_and_conditions, &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 nor 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
144 145 146 |
# File 'lib/cancan/ability.rb', line 144 def can(action = nil, subject = nil, *attributes_and_conditions, &block) add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block)) end |
#can?(action, subject, attribute = nil, *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
You can also pass multiple objects to check. You only need to pass a hash following the pattern { :any => [many subjects] }. The behaviour is check if there is a permission on any of the given objects.
can? :create, {:any => [Project, Rule]}
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.
74 75 76 77 78 79 80 81 |
# File 'lib/cancan/ability.rb', line 74 def can?(action, subject, attribute = nil, *extra_args) match = extract_subjects(subject).lazy.map do |a_subject| relevant_rules_for_match(action, a_subject).detect do |rule| rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute) end end.reject(&:nil?).first match ? match.base_behavior : false end |
#cannot(action = nil, subject = nil, *attributes_and_conditions, &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
160 161 162 |
# File 'lib/cancan/ability.rb', line 160 def cannot(action = nil, subject = nil, *attributes_and_conditions, &block) add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block)) end |
#cannot?(*args) ⇒ Boolean
Convenience method which works the same as “can?” but returns the opposite value.
cannot? :destroy, @project
87 88 89 |
# File 'lib/cancan/ability.rb', line 87 def cannot?(*args) !can?(*args) end |
#extract_rule_in_permissions(permissions_list, rule) ⇒ Object
275 276 277 278 279 280 281 282 |
# File 'lib/cancan/ability.rb', line 275 def (, rule) (rule.actions).each do |action| container = rule.base_behavior ? :can : :cannot rule.subjects.each do |subject| [container][action][subject.to_s] += rule.attributes end end end |
#has_block?(action, subject) ⇒ Boolean
193 194 195 |
# File 'lib/cancan/ability.rb', line 193 def has_block?(action, subject) relevant_rules(action, subject).any?(&:only_block?) end |
#has_raw_sql?(action, subject) ⇒ Boolean
197 198 199 |
# File 'lib/cancan/ability.rb', line 197 def has_raw_sql?(action, subject) relevant_rules(action, subject).any?(&:only_raw_sql?) end |
#merge(ability) ⇒ Object
Copies all rules and aliased actions of the given CanCan::Ability
and adds them to self
.
class ReadAbility
include CanCan::Ability
def initialize
can :read, User
alias_action :show, :index, to: :see
end
end
class WritingAbility
include CanCan::Ability
def initialize
can :edit, User
alias_action :create, :update, to: :modify
end
end
read_ability = ReadAbility.new
read_ability.can? :edit, User.new #=> false
read_ability.merge(WritingAbility.new)
read_ability.can? :edit, User.new #=> true
read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]]
If there are collisions when merging the aliased_actions
, the actions on self
will be overwritten.
class ReadAbility
include CanCan::Ability
def initialize
alias_action :show, :index, to: :see
end
end
class ShowAbility
include CanCan::Ability
def initialize
alias_action :show, to: :see
end
end
read_ability = ReadAbility.new read_ability.merge(ShowAbility) read_ability.aliased_actions #=> [:see => [:show]]
248 249 250 251 252 253 254 |
# File 'lib/cancan/ability.rb', line 248 def merge(ability) ability.rules.each do |rule| add_rule(rule.dup) end @aliased_actions = aliased_actions.merge(ability.aliased_actions) self end |
#model_adapter(model_class, action) ⇒ Object
170 171 172 173 |
# File 'lib/cancan/ability.rb', line 170 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 |
#permissions ⇒ Object
Return a hash of permissions for the user in the format of:
{
can: can_hash,
cannot: cannot_hash
}
Where can_hash and cannot_hash are formatted thusly:
{
action: { subject: [attributes] }
}
266 267 268 269 270 271 272 273 |
# File 'lib/cancan/ability.rb', line 266 def = { can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }, cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } } } rules.each { |rule| (, rule) } end |
#validate_target(target) ⇒ Object
User shouldn’t specify targets with names of real actions or it will cause Seg fault
165 166 167 168 |
# File 'lib/cancan/ability.rb', line 165 def validate_target(target) = "You can't specify target (#{target}) as alias because it is real action name" raise Error, if aliased_actions.values.flatten.include? target end |