Search code examples
ruby-on-railscontrollerpundit

What's the "rails way" to access a resource in a controller's before_action


I'm using Pundit to authorize actions in my controllers. My first try was to authorize the model in an after_action hoook:

class CompaniesController < InheritedResources::Base
  after_action :authorize_company, except: :index

  def authorize_company
    authorize @company
  end

This let me use the default controller actions which define @company so I wouldn't hit the database twice. But, this is bad for destructive actions because it's going to not authorize the action after I've already messed up the database.

So, I've changed to using a before_action hook:

class CompaniesController < InheritedResources::Base
  before_action :authorize_company, except: :index

  def authorize_company
    @company = Company.find(params.require(:id))
    authorize @company
  end

Now, I'm not allowing unauthorized people to delete resources, etc... but I'm hitting the database twice. Is there anyway to access @company without hitting the database twice?


Solution

  • Since your asking for the "rails way" this is how you would set this up in "plain old rails" without InheritedResources.

    class CompaniesController < ApplicationController
      before_action :authorize_company, except: [:new, :index]
    
      def new
        @company = authorize(Company.new)
      end
    
      def index
        @companies = policy_scope(Company)
      end
    
      # ...
    
      private
    
      def authorize_company
        @company = authorize(Company.find(params[:id]))
      end
    end
    

    If you really want to use callbacks you would do it like so:

    class CompaniesController < ApplicationController
      before_action :authorize_company, except: [:new, :index]
      before_action :authorize_companies, only: [:index]
      before_action :build_company, only: [:new]
    
      # ...
    
      private
    
      def authorize_company
        @company = authorize(Company.find(params[:id]))
      end
    
      def authorize_companies
        @companies = policy_scope(Company)
      end
    
      def build_companies
        @company = authorize(Company.new)
      end
    end
    

    Yeah you could write a single callback method with three code branches but this has lower cyclic complexity and each method does a single job.