5

I have a Rails 4 app using Active Admin 1.0.0.pre1 in conjunction with pundit 0.3.0 for authorization which has worked flawlessly thus far, but I'm having trouble figuring out a good way automatically customize forms based on a user's role.

Given these models:

ActiveAdmin.register AdminUser do
  permit_params do
    Pundit.policy(current_admin_user, resource).permitted_attributes
  end

  form do |f|
    f.inputs "Admin Details" do
      f.input :role, as: :select, collection: [:manager, :admin]
      f.input :email, as: :email
      f.input :password
      f.input :password_confirmation
    end
    f.actions
  end
end

class AdminUserPolicy < ApplicationPolicy
  def permitted_attributes
    attributes = [:email, :password, :password_confirmation]
    attributes += [:role] if user.has_role? :super_admin
    attributes
  end
end

I'd like for the role input to be automatically removed from the form.

One option would be something along the lines of:

  permitted_attributes = Pundit.policy(current_admin_user, resource).permitted_attributes

  form do |f|
    f.inputs "Admin Details" do
      f.input :role if permitted_attributes.include? :role
      f.input :email
      f.input :password
      f.input :password_confirmation
    end
    f.actions
  end

but, that approach requires the developer to remember which attributes should be checked, seems prone to forgetfulness and isn't exactly DRY. Perhaps, I am going about this the wrong way? All suggestions welcome.

Ben Carney
  • 61
  • 6
  • 1
    I am 95% sure, the answer hides somewhere along the `main_content` method here: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/views/pages/form.rb. I don't have an open project with AA right now to test it out, but you can override that class and try manually exclude the `:role` input from buider. – Andrey Deineko Apr 07 '15 at 17:26

2 Answers2

1

Intercepting ActiveAdminForm by prepending a module that overrides input with a check against the Pundit policy seems to work well. Here is the implementation I went with:

# /lib/active_admin/permitted_active_admin_form.rb
module PermittedActiveAdminForm
  def permitted_attributes
    policy = Pundit.policy(current_admin_user, resource)
    policy.respond_to?(:permitted_attributes) ? policy.permitted_attributes : []
  end

  def input(*args)
    super(*args) if permitted_attributes.include? args[0]
  end
end


# /config/initializers/active_admin.rb
module ActiveAdmin
  module Views
    class ActiveAdminForm < FormtasticProxy
      prepend PermittedActiveAdminForm
    end
  end
end

# /app/admin/admin_user.rb
ActiveAdmin.register AdminUser do
  permit_params do
    resource ||= AdminUser
    Pundit.policy(current_admin_user, resource).permitted_attributes
  end

  form do |f|
    f.inputs "Admin Details" do
      f.input :role, as: :select, collection: [:manager, :admin]
      f.input :email, as: :email
      f.input :password
      f.input :password_confirmation
    end
    f.actions
  end
end

Thanks to Andrey Deineko for starting me down the right path.

Ben Carney
  • 61
  • 6
  • Do note that there could be issues from the active admin login form because it runs outside of the ActiveAdmin::BaseController codebase. It might work in this case but if you use methods like `active_admin_authorization` to get the authorization adapter and operate on it, it will work on all forms once logged in, but not during log out, worst, it will crash the login view. – tommyalvarez Oct 14 '20 at 18:29
0

I know pundit, not active_admin. With that in mind, using the code you provided, I'll just throw an idea out there.

whitelist = Pundit.policy(current_admin_user, resource).permitted_attributes
fields    = %i( role email password password_confirmation )

form do |f|
  f.inputs "Admin Details" do
    (fields & whitelist).each do |field|
      f.input field
    end
  end
  f.actions
end
deefour
  • 34,974
  • 7
  • 97
  • 90
  • Thanks for the suggestion! The problem here is that the arguments to `f.input` vary depending on the field so I will need call each one explicitly with the correct args. I have updated my example to better reflect that. In the same vain, I considered building a hash where the the field represents the key with the value being the options. Then I could have a helper method loop over the hash to make the correct calls, but I was hoping for a tighter integration. – Ben Carney Apr 10 '15 at 21:11