Search code examples
ruby-on-railsrubypundit

Pundit gem. Is there way to show record's specific attribute values over yml file?


i'am playing with pundit gem. i need to show post's title in a flash message

#config/locales/pundit.en.yml

en:
  pundit:
    default: 'You cannot perform this action.'
    post_policy:
      share?: 'You cannot share post %{post.title}!'

controller:

#posts_controller.rb

def share
  @post = Post.find(params[:id])
  authorize @post
  @post.share
  redirect_to @post
end

all i receive is the exact the same string without any errors and substitutions

You cannot share post %{post.title}!

any suggestions? thanks


Solution

  • The I18n module has no way of knowing that post.title refers to @post.title. Rails does some of that kind of magic with its form helpers, but that magic does not extend to Pundit.

    Here's how the Pundit docs suggest customizing your error messages:

    Creating custom error messages

    NotAuthorizedErrors provide information on what query (e.g. :create?), what record (e.g. an instance of Post), and what policy (e.g. an instance of PostPolicy) caused the error to be raised.

    One way to use these query, record, and policy properties is to connect them with I18n to generate error messages. Here's how you might go about doing that.

    class ApplicationController < ActionController::Base
      rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
    
      private
    
      def user_not_authorized(exception)
        policy_name = exception.policy.class.to_s.underscore
    
        flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
        redirect_to(request.referrer || root_path)
      end
    end
    
    en:
     pundit:
       default: 'You cannot perform this action.'
       post_policy:
         update?: 'You cannot edit this post!'
         create?: 'You cannot create posts!'
    

    Of course, this is just an example. Pundit is agnostic as to how you implement your error messaging.

    From that information we could extrapolate something like the following:

    private
    def user_not_authorized(exception)
      policy_name = exception.policy.class.to_s.underscore
      interpolations = exception.query == 'share?' ? { title: @post.title } : {}
    
      flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default, **interpolations
      redirect_to(request.referrer || root_path)
    end
    

    And then, in your locales:

    en:
      pundit:
        default: You cannot perform this action.
        post_policy:
          share?: You cannot share post %{title}!
    

    I don't have an app with Pundit in front of me so I can't test this; it's likely you'll need to finesse it a bit.