0

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?

user341493
  • 414
  • 1
  • 7
  • 15

2 Answers2

1

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.

max
  • 96,212
  • 14
  • 104
  • 165
0

Turns out rails controllers have a resource if the model exists and build_resource for actions like new.

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

  private

    def authorize_company
      authorize resource
    rescue ActiveRecord::RecordNotFound
      authorize build_resource
    end
end
user341493
  • 414
  • 1
  • 7
  • 15
  • Thats more likely a method of InheritedResources and not Rails. – max Mar 13 '20 at 09:20
  • 1
    And don't do this. `ActiveRecord::RecordNotFound` will be raised if the passed id is not a valid id which is something that will happed in your application if a resource is deleted and somebody follows an old link. Instead of returning the error page and a 404 you're now rendering the show page with a new (blank) resource and will get nil errors galore. – max Mar 13 '20 at 11:13