3

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!

FireDragon
  • 9,325
  • 4
  • 27
  • 34
  • maybe i should use pure ruby and add something like this to my application_policy.rb, which could check if user.is_super_admin? for all actions defined in the class? http://stackoverflow.com/a/5515144/580419 – FireDragon May 07 '15 at 06:20
  • cancancan gem allows this, I don't know why pundit doesn't have something built in. https://github.com/varvet/pundit/issues/191 – konyak Jul 16 '20 at 14:24

1 Answers1

2

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
Community
  • 1
  • 1
deefour
  • 34,974
  • 7
  • 97
  • 90
  • appreciate the tip to define a reusable method in the application policy, but in general i disagree that you would lose clarity or practice poor coding by not putting 'super? || ' in every method, in every Pundit class. Pundit class structures mirror the structure of ActionController::Base -- and this already has helper methods like before_action/after_action. I'm sure as a Rails programmer you probably use before_action a lot and wouldn't say this is confusing or bad programming to do so. So I think the best solution would actually be to mirror these helpers using Ruby-looking into it now :-) – FireDragon May 12 '15 at 08:23
  • in my original question though i was thinking of trying to implement in a single place though, but would agree this is going too far. but writing your own 'before_action' equivalent to use once at the top of each class would be the best solution i feel...keeping things clear, allowing you to not use it for actions if you didn't need it on, and freeing you from repeating the same code a hundred times across many files... – FireDragon May 12 '15 at 08:27
  • +0.5 +0.5 for the first part of your answer! – davegson Aug 11 '15 at 10:10