Rule Aliases
Often, multiple actions require the same authorization logic. Instead of duplicating code, Action Policy provides rule aliases.
The Problem: Duplicated Logic
Look at our current policy:
class ProductPolicy < ApplicationPolicy def update? user.present? end
def edit? user.present? # Same logic! end
def destroy? user.present? # Same again! endendThe edit? action typically has the same requirements as update?. Why write it twice?
Using alias_rule
Action Policy provides alias_rule to solve this:
class ProductPolicy < ApplicationPolicy def update? user.present? end
alias_rule :edit?, to: :update?endNow when authorize! checks edit?, it actually runs update?.
Update Our Policy
Let’s refactor ProductPolicy to use aliases:
# frozen_string_literal: true
class ProductPolicy < ApplicationPolicy def index? true end
def show? true end
def create? user.present? end
alias_rule :new?, to: :create?
def update? user.present? end
alias_rule :edit?, :destroy?, to: :update?endMultiple Aliases at Once
Notice this line:
alias_rule :edit?, :destroy?, to: :update?You can alias multiple rules to the same target! Both edit? and destroy? now use the update? logic.
Common Alias Patterns
A very common pattern is to alias new? to create? since they typically share the same authorization logic. Many developers add this to their ApplicationPolicy:
class ApplicationPolicy < ActionPolicy::Base alias_rule :new?, to: :create?endThis way, all policies automatically allow new? whenever create? passes, unless explicitly overridden.
Why Not Ruby’s alias?
You might wonder: why not just use Ruby’s built-in alias or alias_method?
# Don't do this!alias edit? update?Action Policy’s alias_rule is special because:
- Resolved at authorization time - The alias is resolved when
authorize!is called - Works with caching - Results are cached correctly
- Better for testing - Tests can check the actual rule being applied
- Works with pre-checks - Pre-checks are applied correctly
Test the Aliases
Let’s verify our aliases work in the console:
$ bin/rails consolestore(dev)> user = User.first=> #<User id: 1, email_address: "admin@example.com">store(dev)> product = Product.first=> #<Product id: 1, name: "T-Shirt">store(dev)> policy = ProductPolicy.new(product, user: user)=> #<ProductPolicy:0x...>store(dev)> policy.apply(:update?)=> truestore(dev)> policy.apply(:edit?)=> truestore(dev)> policy.apply(:destroy?)=> trueDefault Rule
You can also set a “catch-all” default rule:
class ProductPolicy < ApplicationPolicy default_rule :manage?
def manage? user.admin? endendNow any undefined rule (like archive?) will fall back to manage?.
Next, let’s learn about pre-checks for adding admin bypasses!
- Preparing Ruby runtime
- Prepare development database
- Starting Rails server