Recently I was working on a Rails 4 project, and much to my surprise, my favorite authorization framework is not supported! CanCan had long been my "go-to" framework for its simplicty and readability. I started searching the Internet for alternative gems, but many of them were also "not-Rails-4-ready" or had not had activity in months. As a Ruby developer, I cannot believe I did not think of this on my own! At the end of the post, the team showcases Pundit - a Ruby gem that encapsulates the content of the blog post with some nice documentation and syntactic-sugar. I tried Pundit, and I really liked it, but it was not going to work... and here is why: Rails policy objects are misunderstood. But first, some background.

The project was a Rails 4 + PostgreSQL stack with OAuth authentication. As with most application developers, I wrote the authentication before the authorization component (you have to login before you can assign permissions). The application used only OmniAuth for authentication, with multi-provider support. If you have ever worked with OAuth, you know that the OmniAuth gem is your savior. That being said, each OAuth provider returns slightly different information in slightly different formats. So you often end up with a huge User model or concern with a bunch of methods that perform almost identical operations with minimal variation.

class User < ActiveRecord :: Base def user_from_facebook ( auth ) User . create! do | user | user . username = auth [ 'info' ][ 'username' ] user . first_name = auth [ 'info' ][ 'first_name' ] user . last_name = auth [ 'info' ][ 'last_name' ] end end def user_from_twitter ( auth ) User . create! do | user | user . username = auth [ 'info' ][ 'nickname' ] user . first_name , user . last_name = auth [ 'info' ][ 'name' ]. split ( ' ' , 2 ) end end def user_from_linkedin ( auth ) # ... end def user_from_github ( auth ) # ... end end

This pattern (Railscast) works really well for a single provider, but becomes a violation of the DRY principle when implementing multi-provider OAuth... I stumbled across Clean OAuth for Rails by David Lesches that uses an Object-Oriented approach and Policy objects to extract information from various OmniAuth auth hashes. If you are familiar with Object-Oriented programming, you effectively create an interface that defines all the common methods, and then each policy object implements that interface, accessing/manipulatin the correct information in the auth hash. So it looks like this (and please read David's full post for a detailed explanation - he does a very good job):

# app/policies/github_policy.rb class GithubPolicy attr_reader :auth def initialize ( auth ) @auth = auth end def first_name auth [ 'info' ][ 'first_name' ] end end

# app/policies/twitter_policy.rb class TwitterPolicy attr_reader :auth def initialize ( auth ) @auth = auth end def first_name auth [ 'info' ][ 'name' ]. split ( ' ' ). first end end

And then in your User model, instantiate an instance of the policy object, and use the declarative interface for a clean, DRY method body.

# app/models/user.rb def self . from_oauth ( auth ) policy = " #{ auth [ 'provider' ] } _policy" . classify . constantize . new ( auth ) create! do | user | user . username = policy . username user . first_name = policy . first_name user . last_name = policy . last_name end end

Awesomesauce. But this actually causes a conflict of interest with Pundit. I added pundit to the Gemfile and started making policy objects as instructed in the README. Wait... I already have policy objects for OmniAuth though. So my app/policies began looking like this:

$ ls -l app/policies account_policy.rb github_policy.rb post_policy.rb twitter_policy.rb user_policy.rb

Woah. Wait, I am confused. So twitter_policy.rb and github_policy.rb are for OmniAuth, but account_policy.rb , post_policy.rb , and user_policy.rb are for Pundit? Just to add to the confusion, the app needed an actual ActiveRecord model for storing Twitter information... So what did twitter_policy.rb actually refer to - the model authorization or the OmniAuth policy?

And thus begins the journey of my refactoring, all of which can be captured in this single statement:

Just because James Golick's original post on policy objects called them "policy objects" does not mean they are "policies".

Let me take a step back - I love the idea of policy objects. But please stop calling them policies; 99% of the time, they aren't actually policies - they are policy objects. So I refactored our OmniAuth policies and Pundit policies to use the idea of policy objects, but called them what they actually are:

OmniAuth Policy: Extractor

Pundit Policy: Authorizer

So first I made a "true" abstract class modeled after the Rails "base class" pattern:

# lib/extractor/base.rb module Extractor class Base class << self # # Finds an extractor object for the given auth hash and returns a new # extractor instance for the auth hash. # # @param [Hash] # the hash returned from omniauth # # @return [~Extractor::Base] # def load ( auth ) provider = auth [ 'provider' ]. classify begin " #{ provider } Extractor" . constantize . new ( auth ) rescue NameError raise RuntimeError , " #{ provider } is not a valid extractor!" end end end # @return [OmniAuth::AuthHash] attr_reader :auth # # Create a new instance of this extractor object. # # @param [OmniAuth::AuthHash] auth # def initialize ( auth ) @auth = auth end # # The unique signature (like composite key) used by ActiveRecord to # identify this extractor. # # @example # { provider: 'github', uid: 'abcd1234' } # # @return [Hash] # def signature { provider: provider , uid: uid } end # # The name for this extractor. # # @return [Symbol] # def provider auth [ 'provider' ] end # # @abstract The first name of the OAuth user. # # @return [String, nil] # def first_name ; end # ...snip... end end

Notice a few key changes:

The Extractor::Base.load method now handles loading a given auth provider, and gracefully fails when it cannot.

Extractor :: Base . load ( auth )

The initialize method is part of the base class; the repetitive code is eliminated. All methods return nil by default.

And an individual extractor (which lives under app/extractors ) looks like this:

# app/extractors/twitter_extractor.rb class TwitterExtractor < Extractor :: Base def first_name auth [ 'info' ][ 'name' ]. split ( ' ' , 2 ). first end # ...snip... end

Extracting this policy into an Extractor object has significantly improved the readability and reuse of this pattern. This is still a policy object, but it is now much clearer what this object does. Additionally, this new pattern is incredibly easy to test. Each extractor has its own spec in spec/extractors :

# spec/extractors/twitter_extractor_spec.rb describe GithubExtractor do let ( :auth ) { OmniAuth . config . mock_auth [ :github ] } subject { described_class . new ( auth ) } its ( :first_name ) { should eq ( 'John' ) } its ( :last_name ) { should eq ( 'Doe' ) } its ( :email ) { should eq ( 'johndoe@example.com' ) } its ( :username ) { should eq ( 'johndoe' ) } its ( :image_url ) { should eq ( 'https://image-url.com' ) } its ( :uid ) { should eq ( '12345' ) } its ( :oauth_token ) { should eq ( 'oauth_token' ) } end

Now that we have moved our OmniAuth policy out of the "policy" namespace, let's apply the same pattern to Pundit. And this is where things went downhill :(. Pundit makes a lot of assumptions about where your code lives, and does not provide an easy way to override those assumptions. And that is totally okay! I think adding such functionality would severly complicate the awesomely-simple codebase and make it less beginner-friendly. So I extracted the ideas behind Pundit into a more custom solution called "Authorizers".

# lib/authorizer/base.rb module Authorizer class Base # @return [User] attr_reader :user # @return [Object] attr_reader :record # # Create a new authorizer for the given user and record. # # @param [User] user # @param [Object] record # def initialize ( user , record ) @user = user || User . new @record = record end # # In development and test, raise an exception if a method is called that # isn't defined on the parent class. In other environments, just return # false, assuming the action is unauthorized. # # @raise [RuntimeError] # in development and test, when an undefined method is called # def method_missing ( m , * args , & block ) if Rails . env . development? || Rails . env . test? raise RuntimeError , " #{ self . class . name } does not define #{ m } !" else false end end end end

Here, you will notice two key differences against Pundit:

The initialize method assumes two objects, where the first is the user object, and the second is the record . Calling an authorization method that hasn't been defined on an authorizer raises a RuntimeError in development/test. In production, it is assumed that the action is unauthorzed.

Why? I am suppressing some code here that fires an alert about the action in a monitoring system and tells a human about it.

And then an individual authorizer looks like:

class PostAuthorizer < Authorizer :: Base def index? true end def show? if user . is? ( :admin ) true else record . user_id == user . id end end def create? record . user_id == user . id end alias_method :new? , :create? end

Just like the extractor pattern, every authorizer inherits from the Base authorizer. Guiding off Thunderbolt Lab's Testing Pundit Policies with RSpec, we can test policies easily. Each policy spec lives under spec/authorizers :

# spec/authorizers/post_authorizer.rb describe PostAuthorizer do let ( :record ) { build ( :post ) } subject { described_class . new ( user , record ) } context 'as an admin' do let ( :user ) { build ( :user , roles: 'admin' ) } it { should permit ( :index ) } it { should permit ( :show ) } it { should permit ( :create ) } end context 'as a user' do let ( :user ) { build ( :user ) } it { should permit ( :index ) } it { should permit ( :show ) } it { should_not permit ( :create ) } context 'when the record is owned by the user' do let ( :record ) { build ( :post , user: user ) } it { should permit ( :create ) } end end context 'as a guest' do let ( :user ) { nil } it { should permit ( :index ) } it { should_not permit ( :show ) } it { should_not permit ( :create ) } it { should_not permit ( :update ) } it { should_not permit ( :destroy ) } end end

This is awesome! Why? Because you have decoupled your authorization tests from the application and Rails. I have worked on many Rails applications where authorization is tested with Capybara by "signing in" as different users, hitting URLs, and asserting page content.

Wrap Up

Advantages

Very "Rails-like" - Controllers, Models, and Views all follow the class SomethingThing < Thing::Base pattern. This makes it less foreign and less magical than pure policy objects. Just like Controllers live under app/controllers , Authorizers live under app/authorizers . Extractors live under app/extractors . See also: Princple of least surprise. Self-contained - All of the code is customized and tailored to the application. Well-tested - Moving these policies into a more Rails-like pattern makes for easy testing and abstraction.

Disadvantages