Skip to content
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

I18n integration #15

Closed
2 tasks
palkan opened this issue Jun 16, 2018 · 7 comments
Closed
2 tasks

I18n integration #15

palkan opened this issue Jun 16, 2018 · 7 comments
Labels
enhancement New feature or request
Milestone

Comments

@palkan
Copy link
Owner

palkan commented Jun 16, 2018

  • Exception message:
rescue_from ActionPolicy::Unauthorized do |ex|
    p ex.message #=> "You do not have access to the stage"
end
  • Reasons integration:
# when we have the reasons object
reasons.details #=> { stage: [:show?] }
# we can generate localized messages with i18n
reasons.full_messages #=> ["You do not have access to the stage"]

In locales:

en:
  action_policy:
    policy:
      stage:
        show?: "You do not have access to the stage"
        # default message for exceptions and reasons
    default_message: "You are not authorized to perform this action"
@palkan palkan added the enhancement New feature or request label Jun 16, 2018
@palkan palkan added this to the 0.2.0 milestone Jun 16, 2018
@brendon
Copy link
Contributor

brendon commented Jul 8, 2018

Hi @palkan, would you like help with this implementation? Currently I'm hacking it like:

rescue_from ActionPolicy::Unauthorized do |exception|
    key = exception.result.reasons.details.flatten.join('.')
    render :inline => t("action_policy.#{key}"),
           :status => 403,
           :layout => 'error'
  end

What are your ideas around how you'd implement this in the gem? :)

@brendon
Copy link
Contributor

brendon commented Jul 8, 2018

An extra consideration around namespaces could be that we first try the namespaced policy in the key, then (if missing) start stripping namespaces off and retrying until there are none so that one doesn't have to repeat data in the translation file.

@palkan
Copy link
Owner Author

palkan commented Jul 11, 2018

Hi @brendon!

What are your ideas around how you'd implement this in the gem? :)

The idea is the following.

First, add a general helper method, say, ActionPolicy::I18n.full_message(policy_class, rule):

def full_message(policy_class, rule)
  # generate candidates
  candidates = [:"policy.#{policy_class.identifier}.#{rule}"]

  # then we have to populate candidates taking into account superclasses
  # and probably namespaces

  candidates << ...
  
  # then add global fallbacks
  candidates << :"policy.#{rule}" # e.g. "action_policy.policy.index?"
  candidates << :default_message

  I18n.t(
    candidates.shift,
    default: candidates,
    scope: [:action_policy]
  )
end

ActiveModel::Errors works the similar way.

Having this we can easily extend Unauthorized class and FailureReasons:

ActionPolicy::Unauthorized.include(Module.new do
  def message
    ActionPolicy::I18n.full_message(policy, rule)
  end
end)

ActionPolicy::Policy::FailureReasons.include(Module.new do
  def full_messages
    reasons.flat_map do |policy_klass, rules|
      rules.map { |rule| ActionPolicy::I18n.full_message(policy_klass, rules) }
    end
  end
end)

The trickiest part here is generating lookup candidates. We should take into account parent policy classes. For example:

# having such policies
class UserPolicy < ActionPolicy::Base
  def index?; end
end

class GuestPolicy < UserPolicy; end

class Admin::UserPolicy < UserPolicy; end

class Admin::GuestPolicy < Admin::UserPolicy; end

# the lookup candidates should be

# for UserPolicy
["user.index?"]

# for GuestPolicy
["guest.index?", "user.index?"]

# for Admin::UserPolicy
["admin/user.index?", "user.index?"]

# for Admin::GuestPolicy
["admin/guest.index?", "admin/user.index?", "guest.index?", "user.index?"]

@brendon
Copy link
Contributor

brendon commented Jul 17, 2018

Thanks @palkan :) If I get some spare time I'll have a go at this :)

@palkan
Copy link
Owner Author

palkan commented Jul 25, 2018

Note to myself:

Add to docs the following instructions on how to configure Rails to store locale files in config/locale/policies/<policy>.yml:

# in config/application.rb
config.i18n.load_path += Dir[
  Rails.root.join('config', 'locales', '**', '*.{yml,rb}').to_s
]

@palkan
Copy link
Owner Author

palkan commented Jul 25, 2018

I've added a basic implementation to my project: https://gist.github.com/palkan/eb6fab36c5f60e899cccacd3d5649a93

Works good; no complex lookup strategies yet.

@brendon
Copy link
Contributor

brendon commented Nov 8, 2018

Well done :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants