5

I'm using pundit for authorization in a rails app. For some models, I want attribute-level authorization. For example, a normal user is allowed to change his phone number but can't set his status to "administrator".

As recommended in the Pundit docs, I'm using the permitted_attributes for this.

I now want to access these attributes in a view to decide which fields to show or enable in a form. Is there an (elegant) way to do this without essentially repeating the authorized fields there?

bmesuere
  • 502
  • 3
  • 12

1 Answers1

5

First you may want to add some nice shortcuts in your ApplicationPolicy which lets you check if attributes are permitted:

class ApplicationPolicy 
  #...

  def permits_attributes?(*attrs)
    attrs.keep_if({|a| permits_attribute?(a) }).any?
  end

  def permits_attribute?(attr)
    permitted_attributes.include?(attr)
  end

  # ...
end

You could then create a custom form builder (or a module that extends a form builder):

class MyFormBuilder < ActionView::Helpers::FormBuilder
  def can_fill_in?(attr)
    yield if @template.policy(object).permits_attribute?(attr)
  end
end

<%= form_for(@project, builder: MyFormBuilder) do |f| %>
  <%= f.can_fill_in?(:title) do %>
    <%= f.label :title %>
    <%= f.text_field :title %>
  <% end %>
<% end %>

You can also configure rails to use your custom form builder by default:

ActionView::Base.default_form_builder = MyFormBuilder

See:

max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks, I added the methods to application policy and created the custom form builder, but I can't seem to access the policy from the helper. I get an "undefined method `policy' for #" – bmesuere Apr 21 '16 at 20:03
  • did you try calling @template.policy? I shaw that i made a typo and wrote it with a space. @template is a link to the view context which should have the policy - not the form builder class. – max Apr 21 '16 at 23:51
  • Yes, I tried both @template.policy and view_context.policy. In the view itself, I can access policy without problem. – bmesuere Apr 22 '16 at 08:16
  • 1
    Nevermind, @template.policy works fine. Because the default_form_builder is set in an initializer, I had to restart rails server for the changes to take effect. – bmesuere Apr 22 '16 at 08:20
  • 1
    There's another small issue with the suggested code. `permitted_attributes.include?(attrs)` doesn't work since attrs is array. This should be something like `attrs.all? { |e| permitted_attributes.include?(e) }` – bmesuere Apr 22 '16 at 09:36
  • This seems to be working however, when the attribute is permitted the field is rendered twice. – Jared Oct 28 '18 at 01:09