Mateusz Urbański
Mateusz Urbański

Reputation: 7862

How to make pundit policies more DRY?

In one of my project I started to using pundit gem and I have a very simply policy that looks like this:

class CompanyPolicy < ApplicationPolicy
  def index?
    true if user.is_a? Administrator
  end

  def new?
    true if user.is_a? Administrator
  end

  def create?
    new?
  end

  def edit?
    true if user.is_a? Administrator
  end

  def update?
    edit?
  end
end

And the question is how can I avoid repeating this:

true if user.is_a? Administrator

Upvotes: 4

Views: 1025

Answers (4)

OuttaSpaceTime
OuttaSpaceTime

Reputation: 966

I combined answers from above and came up with the following:

class ApplicationPolicy
  attr_reader :user

  def initialize(user)
    @user = user
  end

  def self.permit(roles, options)
    return if options[:to].none?

    options[:to].each do |action|
      define_method("#{action}?") do
        return @user.roles? Array.wrap(roles) if options[:when].blank?

        send(options[:when]) and @user.roles? Array.wrap(roles)
      end
    end
  end
end

which allows one to use it like this:

class CommentPolicy < ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @record = record
    super(user)
  end

  permit %i[admin member], to: %i[show edit destroy update], when: :created_by_user

  def created_by_user
    @record.user == @user
  end
end

and

permit :admin, to: %i[index update edit]

works as well

my roles method from user model looks like:

def roles?(user_roles)
    user_roles.each do |role|
      return true if role?(role)
    end
    false
  end

  def role?(role)
    roles.any? { |r| r.name.underscore.to_sym == role }
  end

Upvotes: 0

Brendon Muir
Brendon Muir

Reputation: 4612

You could use alias_method.

class CompanyPolicy < ApplicationPolicy
  def index?
    user.is_a? Administrator
  end

  alias_method :create?, :index?
  alias_method :update?, :index?
end

You have a base class ApplicationPolicy which probably already contains:

def new?
  create?
end

def edit?
  update?
end

so you don't need to repeat these methods in your subclass.

.is_a? returns true or false so no need to explicitly return true if true.

That's a lot more succinct eh? :)

Upvotes: 1

Sebastian
Sebastian

Reputation: 2786

I do trick which looks like this:

class ApplicationPolicy

  private

  def self.permit_owner_to(*actions)
    actions.each do |action|
      define_method("#{action}?") do
        owner?
      end
    end
  end

  def owner?
    # owner logic
  end

end

And used it in other policies

class ItemPolicy < ApplicationPolicy

  permit_owner_to :show, :update, :destroy, :confirm

end

Upvotes: 6

Yule
Yule

Reputation: 9754

I don't actually think you need to remove this. By repeating this you are explicitly saying that this user must be an administrator to access this method. If you did want to though, you could just create a private method.

class CompanyPolicy < ApplicationPolicy
  def index?
    admin?
  end

  def new?
    admin?
  end

  def create?
    new?
  end

  def edit?
    admin?
  end

  def update?
    edit?
  end

  private 
     def admin?
        user.is_a? Administrator
     end
end

Guess this is a matter of personal preference.

Upvotes: 3

Related Questions