Skip to content

@palkan palkan released this Apr 2, 2019 · 2 commits to master since this release

Seattle.rb special.

tl;dr scopes; instrumentation; i18n integration; better testing and debugging.

Features

Scopes

Action Policy provides a universal API for defining different scoping rules: for different kinds of data (e.g. Active Record relations or Action Controller parameters) and different contexts (via the _named scopes).

For example, when you want to scope Active Record collections depending on the current user permissions:

class PostsController < ApplicationController
  def index
    @posts = authorized_scope(Post.all)
  end
end

class PostPolicy < ApplicationPolicy
  relation_scope do |relation|
    next relation if user.admin?
    relation.where(user: user)
  end
end

And then in your test you check that the correct scope has been applied:

describe UsersController do
  subject { get :index }
  
  it "has authorized scope" do
    expect { subject }.to have_authorized_scope(:active_record_relation)
      .with(PostPolicy).with_target { |target|
        # add additional exceptions for the matched target (`Post.all` in our case)
        # if you want to make sure that the scope has been applied to the correct data
      }
  end
end

πŸ“– Read the docs.

Failure reasons enhancements: full_message and arbitrary details

When the i18n gem is available in your app, the reasons object implements the #full_messages and the result object implements the #message method, which generates the failure messages using the locales (similarly to ActiveMode::Errors):

action_policy:
  policy:
    post:
      show?: "You cannot view this post"
class ApplicationController < ActionController::Base
  rescue_from ActionPolicy::Unauthorized do |ex|
    p ex.result.reasons.full_messages #=> ["You cannot view this post"]
  end
end

πŸ“– Read the docs.

You can now add a custom meta data to your failure reasons to provide more context:

  class ApplicantPolicy < ApplicationPolicy
    def show?
      allowed_to?(:show?, object.stage)
    end
  end

class StagePolicy < ApplicationPolicy
  def show?
    # Add stage title to the failure reason (if any)
    # (could be used by client to show more descriptive message)
    details[:title] = record.title
    # then perform the checks
    user.stages.where(id: record.id).exists?
  end
end

πŸ“– Read the docs.

RSpec DSL

Custom DSL for writing policy specs has been addedL

describe PostPolicy do
  let(:user) { build_stubbed :user }
  let(:record) { build_stubbed :post, draft: false }

  let(:context) { {user: user} }

  describe_rule :show? do
    succeed "when post is published"

    failed "when post is draft" do
      before { post.draft = false }

      succeed "when user is a manager" do
        before { user.role = "manager" }
      end
    end
  end
end

πŸ“– Read the docs.

Debugging helpers

Added Policy#pp(rule) method to print the annotated rule source code.

It aims to help debugging complex policy rules:

def edit?
  binding.pry
  (user.name == "John") && (admin? || access_feed?)
end
pry> pp :edit?
MyPolicy#edit?
↳ (
    user.name == "John" #=> false
  )
  AND
  (
    admin? #=> false
    OR
    access_feed? #=> true
  )
)

πŸ“– Read the docs and check the PR#63.

Other

  • Added ActiveSupport-based instrumentation.

πŸ“– Read the docs.

  • Added #implicit_authorization_target.

    See #35.

    Implicit authorization target (defined by implicit_authorization_target) is used when no target specified for authorize! call.

    For example, for Rails controllers integration it's just controller_name.classify.safe_constantize.

  • Added Symbol lookup to the lookup chain.

For instance, lookup will implicitly use AdminPolicy in a following case:

# admin_controller.rb
class AdminController < ApplicationController
  authorize! :admin, to: :update_settings
end
  • Adde ability to pass authorization context explicitly.

Now it's possible to override implicit authorization context
via context option:

authorize! target, to: :show?, context: {user: another_user}
authorized_scope User.all, context: {user: another_user}
Assets 2
You can’t perform that action at this time.