Action Policy allows you to add rule aliases. It is useful when you rely on implicit rules in controllers. For example:
class PostsController < ApplicationController
before_action :load_post, only: [:edit, :update, :destroy]
private
def load_post
@post = Post.find(params[:id])
# depending on action, an `edit?`, `update?` or `destroy?`
# rule would be applied
authorize! @post
end
end
In your policy, you can create aliases to avoid duplication:
class PostPolicy < ApplicationPolicy
alias_rule :edit?, :destroy?, to: :update?
end
NOTE: alias_rule
is available only if you inherit from ActionPolicy::Base
or include ActionPolicy::Policy::Aliases
into your ApplicationPolicy
.
Why not just use Ruby's alias
?
An alias created with alias_rule
is resolved at authorization time (during an authorize!
or allowed_to?
call), and it does not add an alias method to the class.
That allows us to write tests easier, as we should only test the rule, not the alias–and to leverage caching better.
By default, ActionPolicy::Base
adds one alias: alias_rule :new?, to: :create?
.
You can add a default rule–the rule that would be applied if the rule specified during authorization is missing (like a "wildcard" alias):
class PostPolicy < ApplicationPolicy
# For an ApplicationPolicy, makes :manage? match anything that is
# not :index?, :create? or :new?
default_rule :manage?
# If you want manage? to catch really everything, place this alias
#alias_rule :index?, :create?, :new?, to: :manage?
def manage?
# ...
end
end
Now when you call authorize! post
with any rule not defined in the policy class, the manage?
rule is applied. Note that index?
create?
and new?
are already defined in the superclass by default (returning false
) - if you want the same behaviour for all actions, define aliases like in the example above (commented out).
By default, ActionPolicy::Base
sets manage?
as a default rule.
In case you forget to add ?
at the end of :new
in a code like allowed_to?(:new, User)
, a wrong rule will be called. Instead of the:new?
, the :manage?
rule will be applied. You can set ActionPolicy.enforce_predicate_rules_naming = true
to raise an error when the called rule doesn't end with a question mark.
Rules in action_policy
can only be public methods. Trying to use a private method as a rule will raise an error. Thus, aliases can also only point to public methods.
Here's the order in which aliases and concrete rule methods are resolved in regards to subclasses:
- If there is a concrete rule method on the subclass, this is called, else
- If there is a matching alias then this is called, else
- When aliases are defined on the subclass they will overwrite matching aliases on the superclass.
- If there is a concrete rule method on the superclass, then this is called, else
- If there is a default rule defined, then this is called, else
- If the default rule is unaltered, then the
manage?
rule is called - If the default rule is set to
nil
,ActionPolicy::UnknownRule
is raised.
Here's an example with the expected results:
class SuperPolicy < ApplicationPolicy
alias_rule :update?, :destroy?, :create?, to: :edit?
def manage?
end
def edit?
end
def index?
end
end
class SubPolicy < AbstractPolicy
default_rule nil
alias_rule :index?, :update?, to: :manage?
def create?
end
end
Authorizing against the SuperPolicy:
update?
will resolve toedit?
destroy?
will resolve toedit?
create?
will resolve toedit?
manage?
will resolve tomanage?
edit?
will resolve toedit?
index?
will resolve toindex?
something?
will resolve to thedefault_rule
:manage?
Authorizing against the SubPolicy:
index?
will resolve tomanage?
update?
will resolve tomanage?
create?
will resolve tocreate?
destroy?
will resolve toedit?
manage?
will resolve tomanage?
edit?
will resolve toedit?
index?
will resolve tomanage?
something?
will raiseActionPolicy::UnknownRule