diff --git a/lib/action_policy/behaviours/policy_for.rb b/lib/action_policy/behaviours/policy_for.rb index 4abc54f..a33d69f 100644 --- a/lib/action_policy/behaviours/policy_for.rb +++ b/lib/action_policy/behaviours/policy_for.rb @@ -9,7 +9,7 @@ module PolicyFor # Returns policy instance for the record. def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace) - context = context ? authorization_context.merge(context) : authorization_context + context = context ? build_authorization_context.merge(context) : authorization_context policy_class = with || ::ActionPolicy.lookup( record, @@ -18,8 +18,10 @@ def policy_for(record:, with: nil, namespace: authorization_namespace, context: policy_class&.new(record, **context) end - def authorization_context - Kernel.raise NotImplementedError, "Please, define `authorization_context` method!" + def authorization_context = @authorization_context ||= build_authorization_context + + def build_authorization_context + Kernel.raise NotImplementedError, "Please, define `build_authorization_context` method!" end def authorization_namespace diff --git a/lib/action_policy/policy/authorization.rb b/lib/action_policy/policy/authorization.rb index 52fd7ae..8c408bd 100644 --- a/lib/action_policy/policy/authorization.rb +++ b/lib/action_policy/policy/authorization.rb @@ -42,6 +42,7 @@ def included(base) end attr_reader :authorization_context + alias_method :build_authorization_context, :authorization_context def initialize(record = nil, **params) super(record) diff --git a/test/action_policy/behaviour_test.rb b/test/action_policy/behaviour_test.rb index 03b0ec4..abbadce 100644 --- a/test/action_policy/behaviour_test.rb +++ b/test/action_policy/behaviour_test.rb @@ -335,6 +335,24 @@ def test_strict_namespace_authorization_policy_class end class TestAuthorizationContext < Minitest::Test + class AdminPolicy + include ActionPolicy::Policy::Core + include ActionPolicy::Policy::Authorization + + authorize :user + + def show? = user.name == "admin" || record != "admin" + end + + class ChatPolicy < AdminPolicy + include ActionPolicy::Policy::Core + include ActionPolicy::Policy::Authorization + + authorize :user, :account + + def speak? = user.name == account + end + class ChatChannel include ActionPolicy::Behaviour @@ -347,9 +365,26 @@ def initialize(name) @user = User.new(name) end + def speak(word, to:) + # Use explicit context to skip memoization + authorize! to, to: :show?, with: AdminPolicy, context: {} + + @explicit_account = to + # This will use implicit context (shouldn't be memoized) + # See https://github.com/palkan/action_policy/issues/265 + authorize! to: :speak? + + "I have spoken #{word} to ##{to}" + end + def account + return @explicit_account if defined?(@explicit_account) @account = @account ? @account.succ : "a" end + + def implicit_authorization_target = self + + def policy_class = ChatPolicy end class ChutChannel < ChatChannel @@ -371,4 +406,12 @@ def test_authorization_context_without_memoization assert_equal({user: User.new("guest"), account: "a"}, channel.authorization_context) assert_equal({user: User.new("guest"), account: "b"}, channel.authorization_context) end + + def test_authorization_context_with_explicit_context + memoize_free_channel = ChutChannel.new("pilot") + assert_equal "I have spoken yohanga to #pilot", memoize_free_channel.speak("yohanga", to: "pilot") + + channel = ChatChannel.new("bart") + assert_equal "I have spoken karamba to #bart", channel.speak("karamba", to: "bart") + end end