-
Notifications
You must be signed in to change notification settings - Fork 641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ability to use different error messages for same policy rule #625
Conversation
@holyketzer Interesting suggestion. I guess you're referring to https://github.com/varvet/pundit#creating-custom-error-messages, which shows an approach allowing a single message to be assigned to each action. I'm not sure about the implementation in this PR: adding these methods to the ApplicationPolicy and passing the message using an instance variable feels a bit too obscure for me. Since the class ProjectPolicy < ApplicationPolicy
def create?
if user.has_paid_subscription?
if user.project_limit_reached?
raise(Pundit::NotAuthorizedError, message: I18n.t('errors.user.project_limit_reached'))
else
true
end
else
raise(Pundit::NotAuthorizedError, message: I18n.t('errors.user.paid_subscription_required'))
end
end
end Or even just eg. raise(Pundit::NotAuthorizedError, I18n.t('errors.user.paid_subscription_required')) ...since I don't see how passing the message as part of the options is any different to just passing it as a string. Maybe this is bad since it's failing using an error rather than I'd be open to adding a method to |
Or maybe my suggestion doesn't work since |
Yes I started from it, but later application evolved and we faced with requirement to have different messages for one policy
Yes, I understand it, including Pundit philosophy to keep things very simple.
IMHO from policy point of view returning
Cool, sounds like a good compromise. Let me investigate it a little bit more.
It should work, yes it works |
Right now I inherited custom error class to split errors in controller, like this. class CutomNotAuthorizedError < Pundit::NotAuthorizedError
def initialize(custom_message)
super(custom_message)
end
end But this also works: # inside my policy
raise Pundit::NotAuthorizedError, custom_message rescue_from Pundit::NotAuthorizedError do |e|
message =
if e.policy
policy_name = e.policy.class.to_s.underscore
I18n.t("#{policy_name}.#{e.query}", scope: "pundit", default: :default)
else
# if there is no policy in exception options it means custom error was raised
e.message
end
# render with message ...
end Which options I can propose:
|
If what you want is to sometimes have a single message and sometimes have multiple messages, then this approach of using a separate class feels more explicit: the Observe that at this point all we're using the error class for is to pass information from the policy up to the controller, so it doesn't really matter what the error is: I'm not even sure it needs to inherit from But is this really what you want? This means that in some cases the message is configured in the controller and in some places in the policy - would it be easier to manage if all the messages were at the same level (in the policies)? |
Here's some thinking aloud: There are a number of different ways that we've discussed building messages at various points here:
|
@holyketzer question: do the messages need to be different per controller? Or should the same authorisation failure have the same message regardless of where it happens? |
Yes, for example for some policy actions we have couple of checks can user access this resource (does it belong to this user) and does use have paid subscription which allows to do it.
Correct, the way how I implemented it now, it doesn't.
Hm, yes, I would prefer to move this logic into one place - policy and just consume exception message in rescue controller block. I can generalise the way how base |
@holyketzer What do you think the way forward is? Reading back, it looks like the solution that we arrived on is to have pundit policies explicitly raise errors with specific messages, and then rescue these in the controller to display the message. That solution required no changes to Pundit, but looking at it now I'm not sure it works: is I18n available in policies? If not then maybe an idea could be to store a code for the type of authorisation on the error message and use that to look up in I18n - that way the pundit policy stays nice and isolated. One way to do this might be to add an additional option onto class ProjectPolicy < ApplicationPolicy
def create?
if user.has_paid_subscription?
if user.project_limit_reached?
raise(Pundit::NotAuthorizedError, reason: 'user.project_limit_reached')
else
true
end
else
raise(Pundit::NotAuthorizedError, reason: 'user.paid_subscription_required')
end
end
end class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized(exception)
message = exception.reason ? t "errors.#{exception.reason}" : exception.message
flash[:error] = message, scope: "pundit", default: :default
redirect_to(request.referrer || root_path)
end
end A nice property of this is that |
c1efd79
to
973b63b
Compare
I18n is available in policies if you refer it with full module name
I like this approach, it looks simplest one, I implemented it, and check on my my project, looks good. |
@holyketzer great - I guess we can close this now? |
I changed PR did you see it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work 👍 - a couple of tweaks maybe.
README.md
Outdated
@@ -538,6 +538,37 @@ en: | |||
Of course, this is just an example. Pundit is agnostic as to how you implement | |||
your error messaging. | |||
|
|||
## Deep error messages customisation | |||
|
|||
If you have different authorization deny reasons you want to inform user with descriptive message then: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"deny reason" feels like a new bit of terminology: I'd prefer to avoid that if possible. Consider:
If you have different authorization deny reasons you want to inform user with descriptive message then: | |
If there are multiple reasons that authorization can be denied, you can show different messages by raising exceptions in your policy: |
README.md
Outdated
@@ -538,6 +538,37 @@ en: | |||
Of course, this is just an example. Pundit is agnostic as to how you implement | |||
your error messaging. | |||
|
|||
## Deep error messages customisation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure "Deep" feels like a familiar term for me. Maybe something like "Multiple error messages".
Also consider nesting this under the previous section.
flash[:error] = message, scope: "pundit", default: :default | ||
redirect_to(request.referrer || root_path) | ||
end | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth including an example locale file like in the previous section, or is that unnecessary?
@Linuus any thoughts on the PR as it now stands? (no need to understand the discussion: this is now just documentation). |
Ok, done |
This is definitely a feature that I appreciate and will be using. Are there any plans to release a new version of the gem soon including this? (just asking, otherwise I can just point to this PR or master for now). Thanks in advance. |
@K1N5L4Y3R this is largely just documentation and there's probably nothing stopping you from applying the approach now. To be honest I'm less and less convinced that extending Pundit::NotAuthorizedError was a good idea: you can most likely get the same result by making your own class that inherits from Pundit::NotAuthorizedError and includes a 'reason' key. |
This reverts commit 6be4621, reversing changes made to 872ed68. Reverting this because it's blocking us from making a new release, see: #656 (comment)
Revert "Merge pull request #625 from holyketzer/custom-messages"
The pull request (varvet/pundit#625) that adds a "reason" to Pundit::NotAuthorizedError was merged, but subsequently reverted prior to the release of Pundit 2.3.0. As of now, the maintainers are soliciting feedback and evaluating alternative approaches to support custom error messages. It appears increasingly unlikely that Pundit will support the "reason" API that we are currently dependent on. So, we're just adding the "reason" feature ourselves with this commit.
The pull request (varvet/pundit#625) that adds a "reason" to Pundit::NotAuthorizedError was merged, but subsequently reverted prior to the release of Pundit 2.3.0. As of now, the maintainers are soliciting feedback and evaluating alternative approaches to support custom error messages. It appears increasingly unlikely that Pundit will support the "reason" API that we are currently dependent on. So, we're just adding the "reason" feature ourselves with this commit.
Deep error messages customisation
There is no way to show different error messages for one policy rule now, but is matters (see example below).
When you have different authorization deny reasons you want to inform user with descriptive message then:
Extend your
ApplicationPolicy
withmessage
attr anddeny!
method:In your policy class specify custom error message in
deny!
parameter:Then you can get this error message in exception handler: