Skip to content

Commit

Permalink
Separate camelize and classify lookup
Browse files Browse the repository at this point in the history
Previously, if you were using ActiveSupport, the lookup would use Rails'
`String#classify` method. Without ActiveSupport it'd use a patched
version which implemented classifying as camelizing a String.

As an example, if the lookup was given a symbol `:users`, it'd behave as
follows:
a) Without ActiveSupport, look for `UsersPolicy`;
b) With ActiveSupport, look for `UserPolicy`;

This change does three things. First, we add a new lookup that uses
`camelize`. This enables the lookup to find `UsersPolicy` when given
`:users` in Rails. This is the new behaviour.

Second, by changing the Symbol extension, we keep the old behaviour in
the non-Rails working.

Finally, we add a new lookup that is only used when `classify` is
available on String and uses that to infer the polict. This ensures that
finding `UserPolicy` still works in the case where we're in a Rails
application.
  • Loading branch information
Bengt-Ove Holländer authored and palkan committed Jun 12, 2020
1 parent d99d428 commit 588daef
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 27 deletions.
7 changes: 6 additions & 1 deletion docs/lookup_chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

Action Policy tries to automatically infer policy class from the target using the following _probes_:

1. If the target is a `Symbol`, then use `"#{target.to_s.classify}Policy"` as a `policy_name` (see below);
1. If the target is a `Symbol`:

a) Try `"#{target.to_s.camelize}Policy"` as a `policy_name` (see below);

b) If `String#classify` is available, e.g. when using Rails' ActiveSupport, try `"#{target.to_s.classify}Policy"`;

2. If the target responds to `policy_class`, then use it;
3. If the target's class responds to `policy_class`, then use it;
4. If the target or the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

module ActionPolicy
module Ext
# Add `classify` to Symbol
module SymbolClassify
# Add `camelize` to Symbol
module SymbolCamelize
refine Symbol do
if "".respond_to?(:classify)
def classify
to_s.classify
if "".respond_to?(:camelize)
def camelize
to_s.camelize
end
else
def classify
def camelize
word = to_s.capitalize
word.gsub!(/(?:_)([a-z\d]*)/) { $1.capitalize }
word
Expand Down
21 changes: 17 additions & 4 deletions lib/action_policy/lookup_chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ module LookupChain
using ActionPolicy::Ext::StringConstantize
end

require "action_policy/ext/symbol_classify"
using ActionPolicy::Ext::SymbolClassify
require "action_policy/ext/symbol_camelize"
using ActionPolicy::Ext::SymbolCamelize

require "action_policy/ext/module_namespace"
using ActionPolicy::Ext::ModuleNamespace
Expand Down Expand Up @@ -114,7 +114,19 @@ def policy_class_name_for(record)
SYMBOL_LOOKUP = ->(record, namespace: nil, **) {
next unless record.is_a?(Symbol)

policy_name = "#{record.classify}Policy"
policy_name = "#{record.camelize}Policy"
if namespace.nil?
policy_name.safe_constantize
else
lookup_within_namespace(policy_name, namespace)
end
}

# (Optional) Infer using String#classify if available
CLASSIFY_SYMBOL_LOOKUP = ->(record, namespace: nil, **) {
next unless record.is_a?(Symbol)

policy_name = "#{record.to_s.classify}Policy"
if namespace.nil?
policy_name.safe_constantize
else
Expand All @@ -124,10 +136,11 @@ def policy_class_name_for(record)

self.chain = [
SYMBOL_LOOKUP,
(CLASSIFY_SYMBOL_LOOKUP if String.method_defined?(:classify)),
INSTANCE_POLICY_CLASS,
CLASS_POLICY_CLASS,
NAMESPACE_LOOKUP,
INFER_FROM_CLASS
]
].compact
end
end
16 changes: 16 additions & 0 deletions test/action_policy/ext/symbol_camelize_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require "test_helper"

require "action_policy/ext/symbol_camelize"
using ActionPolicy::Ext::SymbolCamelize

class TestSymbolClassify < Minitest::Test
def test_simple_name
assert_equal "Test", :test.camelize
end

def test_underscored_name
assert_equal "TeStO", :te_st_o.camelize
end
end
16 changes: 0 additions & 16 deletions test/action_policy/ext/symbol_classify_test.rb

This file was deleted.

8 changes: 8 additions & 0 deletions test/action_policy/lookup_chain_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def self.policy_class

class LookupAPolicy; end
class LookupBPolicy; end
class LookupBsPolicy; end

module LookupNamespace
class LookupAPolicy; end
Expand Down Expand Up @@ -54,6 +55,13 @@ def test_symbol
)
end

def test_symbol_exact_match
assert_equal(
LookupBsPolicy,
ActionPolicy.lookup(:lookup_bs)
)
end

def test_symbol_namespaced
assert_equal(
LookupNamespace::LookupAPolicy,
Expand Down
1 change: 1 addition & 0 deletions test/action_policy/rails/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

require "rails"
require "action_controller/railtie"
require "action_policy"
require "action_policy/railtie"

Bundler.require(*Rails.groups)
Expand Down
26 changes: 26 additions & 0 deletions test/action_policy/rails/lookup_chain_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

# Skip this test if the lookup chain is already defined. This is the case when
# *not* running in isolated mode (`bundle exec rake test:isolated`).
return if defined?(ActionPolicy::LookupChain)

require_relative "./dummy/config/environment"
require "test_helper"

class EntityPolicy; end

class TestLookupChain < Minitest::Test
def test_symbol_singular
assert_equal(
EntityPolicy,
ActionPolicy.lookup(:entity)
)
end

def test_symbol_plural
assert_equal(
EntityPolicy,
ActionPolicy.lookup(:entities)
)
end
end

0 comments on commit 588daef

Please sign in to comment.