FireDragon
FireDragon

Reputation: 9385

Using Pundit for all-access "super_admin" role

I'm wondering the best/simplest way to give a user "super_admin" access using the Pundit gem -- or, what's the simplest way to give a user access to all controller actions across the site?

I realize I can edit the policy file for each controller, then add something like

def delete?
  user.is_super_admin?
end

...to each and every action, in each and every controller. But is there a single place where I could define this?

I've taken a look at the application policy, which all of my other controller policies inherit from, but I believe these actions are all overridden in my controllers anyway so I don't think I could define it there.

I'm sure others have needed to implement this feature? What's the simplest way? Thanks!

Upvotes: 3

Views: 1069

Answers (1)

deefour
deefour

Reputation: 35370

For the (in reality) minimal annoyance that comes from retyping a method name a couple dozen times through the life of the project, I think the maybe not-so-obvious benefit is worth it.

First, you could save a few keystrokes by defining a super? method on your ApplicationPolicy

def super?
  user.is_super_admin?
end

And then your delete? and other methods could be simplified just a bit.

def delete?
  super? || user.id == record.user_id # super admin or owner of the resource
end

Pundit policies are simple enough. If I have never worked in your code before, I can look at the above method and know exactly who can perform the deletion.

Sure, it might save you a couple keystrokes to have some meta-programmed functionality to call super? automagically, with delete? appearing as

def delete?
  user.id == record.user_id # super admin or owner of the resource
end

but now the clarity is lost. Also, what if there is a method down the road you do not want the super admin to have access to? Reworking your already more complicated solution would make things even more prone to bugs and more difficult to understand. All so ~10 keystrokes per method could be saved.


Update

Opinions on code readability aside, here is one implementation that does what you seem to be after. You might also look into hooking the method calls instead of removing the ? from the method names as I've done below to make sure method_missing picks them up.

#
# Classes
#

User = Struct.new(:id, :name)
Foo  = Struct.new(:id, :title, :user_id)

class SuperUser < User; end

ApplicationPolicy = Struct.new(:user, :record) do

  attr_reader :user, :record

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

  def method_missing(name)
    raise NoMethodError, "No method `#{name}` for #{self}" if (name =~ /\?$/).nil?

    user.is_a?(SuperUser) || send(name.to_s.sub(/\?$/, ""))
  end

end

class FooPolicy < ApplicationPolicy

  private

    def edit
      user.id == record.user_id
    end

end


#
# Fixtures
#

user = User.new(1, "Deefour")
foo  = Foo.new(1, "A Sample Foo", 1)

super_user = SuperUser.new(2, "FireDragon")


#
# Examples
#

FooPolicy.new(user, foo).edit?       #=> true
FooPolicy.new(super_user, foo).edit? #=> true

Upvotes: 2

Related Questions