controller_policies 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bce0f759505e11b5f05fd9d220d1f8553a1b1a281f50a40b51533a160a225fd
4
- data.tar.gz: e5f9d53c8bfe61e53e546c760fe0eba07d298717f5306f99575da4c8f60c4fb5
3
+ metadata.gz: 85d80d00d5bb31de269ac2190cd44a77e9d2aa1623aa7d6e00cf86de12565baa
4
+ data.tar.gz: ec23ef9100a962f17386693889903182f84cbb247c9dc275da54cec789acc445
5
5
  SHA512:
6
- metadata.gz: 2b25df0839fdc02faf866a769f1102175ecda41fbf6e018ea735c981c712354270768af5770dc266cc9fc998cddc0ae6316c90b54570fb5faa4abf06c8c43d8b
7
- data.tar.gz: da0373ebea81108926b63a898b0b9955aff0a9383a2fd4e49d7da0ea67c6784429550edb9d1312c34534a50aa4bc531bc1002f5f606b796431e773fa494f7827
6
+ metadata.gz: 9ec83f851cc09988e4664c404b2e99b2a8c48e954f3804652fb0477f66545d75b12887261e4b72c265da16dd3c8ce277e5fdcf6d170eea535b7dfe07c53aa2e8
7
+ data.tar.gz: 6afa4ececa6d5c04164eafc5c66a578290d6ff3edfcffff76260f6bb645450000b2177d33b49a9d3105eb8f6463a0cda3902a7caee3baa4acfa665b0395e9f39
data/README.md CHANGED
@@ -20,6 +20,16 @@ rails g policy_definition my/namespace
20
20
 
21
21
  This will generate a file: `app/policies/my/namespace/definitions.rb`
22
22
 
23
+ For controllers without a namespace (root-level controllers), you can generate a root-level definitions file:
24
+
25
+ ```sh
26
+ rails g policy_definition root
27
+ # or simply
28
+ rails g policy_definition
29
+ ```
30
+
31
+ This will generate a file: `app/policies/definitions.rb`
32
+
23
33
  The developer should edit this file and add the policies for the app. **It is important to note that the location of the definitions file should reflect the namespace of the associated controllers.**
24
34
 
25
35
  ### `actions` key
@@ -43,16 +53,35 @@ end
43
53
 
44
54
  The above definition will have enforced policies on `Base::FeatureApp::UsersController` (all actions), `Base::DataApp::ProductsController` (`index` action) and `Base::SubscriptionController`.
45
55
 
56
+ For root-level controllers (controllers without a namespace), you can define policies directly in the `Policies` module:
57
+
58
+ ```ruby
59
+ module Policies
60
+ DEFINITIONS = [
61
+ {
62
+ code: 'user_management',
63
+ name: 'User Management',
64
+ description: 'Allows user management operations',
65
+ actions: ['users', 'profiles#show', 'dashboard']
66
+ }
67
+ ]
68
+ end
69
+ ```
70
+
71
+ The above definition will have enforced policies on `UsersController` (all actions), `ProfilesController` (`show` action) and `DashboardController`.
72
+
46
73
  **Do note that the `actions` array implement *Regular Expression Matching*. That means that if you have multiple controllers with the same name on their parent namespace, the parent will be matched first.** To avoid this problem, simply add the namespace to match the intended child instead.
47
74
 
48
75
  ```ruby
49
- module Base
50
- DEFINITIONS = {
51
- code: 'policy_code',
52
- name: 'Policy Name',
53
- description: 'I am a policy.',
54
- actions: ['feature_app/users', 'another_base/data_app/products#index', 'base/subscriptions']
55
- }
76
+ module Policies
77
+ module Base
78
+ DEFINITIONS = {
79
+ code: 'policy_code',
80
+ name: 'Policy Name',
81
+ description: 'I am a policy.',
82
+ actions: ['feature_app/users', 'another_base/data_app/products#index', 'base/subscriptions']
83
+ }
84
+ end
56
85
  end
57
86
  ```
58
87
 
data/lib/ability.rb CHANGED
@@ -34,36 +34,28 @@ class Ability
34
34
 
35
35
  # Filter abilities based on namespace.
36
36
  def where(*queries)
37
- results = []
38
- queries.each do |query|
39
- case query.class.to_s
40
- when 'String'
41
- results += all.select { |ability| ability.namespace.to_s == "Policies::#{trim(query).camelize}" }
42
- when 'Module', 'Class'
43
- results += all.select { |ability| ability.namespace == query }
37
+ queries.flat_map do |query|
38
+ case query
39
+ when String
40
+ filter_by_string_query(query)
41
+ when Module, Class
42
+ all.select { |ability| ability.namespace == query }
43
+ else
44
+ []
44
45
  end
45
46
  end
46
- results
47
47
  end
48
48
 
49
- # Find an ability within a namespace.
50
- # def find(query_string)
51
- # where(query_string).first
52
- # end
53
-
54
49
  # Match abilities based on a matching string or regex. The matcher is based on the namespace.
55
50
  def match(expression)
56
- case expression.class.to_s
57
- when 'String' then all.select { |ability| ability.namespace.to_s.match?(/#{trim(expression).camelize}/) }
58
- when 'Regexp' then regex_matcher(expression)
51
+ case expression
52
+ when String
53
+ all.select { |ability| ability.namespace.to_s.match?(/#{trim(expression).camelize}/) }
54
+ when Regexp
55
+ regex_matcher(expression)
59
56
  end
60
57
  end
61
58
 
62
- # Find an ability based on a matching string or regex. The matcher is based on the namespace.
63
- # def mill(expression)
64
- # match(expression).first
65
- # end
66
-
67
59
  # Path to the policy folder.
68
60
  def policy_path
69
61
  @policy_path ||= Rails.root.join('app/policies')
@@ -71,6 +63,15 @@ class Ability
71
63
 
72
64
  private
73
65
 
66
+ def filter_by_string_query(query)
67
+ if query.empty? || query == '/'
68
+ all.select { |ability| ability.namespace == Policies }
69
+ else
70
+ target_namespace = "Policies::#{trim(query).camelize}"
71
+ all.select { |ability| ability.namespace.to_s == target_namespace }
72
+ end
73
+ end
74
+
74
75
  def definitions
75
76
  @definitions ||= begin
76
77
  data = []
@@ -82,14 +83,20 @@ class Ability
82
83
  end
83
84
 
84
85
  def definition_files_post_processing(file_path)
85
- module_constant = "Policies::#{convert_namespace(file_path)}".constantize
86
- policy_definitions = module_constant::DEFINITIONS
87
- policy_definitions.map do |policy_definition|
88
- policy_definition[:namespace] = module_constant
89
- policy_definition
86
+ namespace_path = convert_namespace(file_path)
87
+ module_constant = resolve_module_constant(namespace_path)
88
+
89
+ module_constant::DEFINITIONS.map do |policy_definition|
90
+ policy_definition.merge(namespace: module_constant)
90
91
  end
91
92
  end
92
93
 
94
+ def resolve_module_constant(namespace_path)
95
+ return Policies if namespace_path.empty?
96
+
97
+ "Policies::#{namespace_path}".constantize
98
+ end
99
+
93
100
  def regex_matcher(expression)
94
101
  all.select do |ability|
95
102
  ability.namespace.to_s.match?(expression) || ability.namespace.to_s.underscore.match?(expression)
@@ -34,11 +34,23 @@ module ControllerPolicies
34
34
  # Method that attempts to go through policy definitions and check if it matches the current controller.
35
35
  # If it finds a match, it will be an applicable definition to check abilities for.
36
36
  def applicable_abilities
37
- @applicable_abilities ||= Ability.where(controller_namespace).select do |ability|
38
- matching_actions = ability.actions.select { |action| /#{action}/.match?("#{controller_path}##{action_name}") }
39
- matching_actions.present?
37
+ @applicable_abilities ||= begin
38
+ namespace_abilities = fetch_namespace_abilities
39
+ namespace_abilities.select { |ability| ability_matches_current_action?(ability) }
40
40
  end
41
41
  end
42
+
43
+ # Get abilities for the current controller namespace, or root if no namespace
44
+ def fetch_namespace_abilities
45
+ namespace = controller_namespace.empty? ? '' : controller_namespace
46
+ Ability.where(namespace)
47
+ end
48
+
49
+ # Check if an ability matches the current controller action
50
+ def ability_matches_current_action?(ability)
51
+ current_controller_action = "#{controller_path}##{action_name}"
52
+ ability.actions.any? { |action| /#{action}/.match?(current_controller_action) }
53
+ end
42
54
  end
43
55
  end
44
56
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ControllerPolicies
4
- VERSION = '1.0.1'
4
+ VERSION = '1.1.1'
5
5
  end
@@ -3,18 +3,22 @@
3
3
  # Generates a definition file for a namespace.
4
4
  class PolicyDefinitionGenerator < Rails::Generators::Base
5
5
  source_root File.expand_path('templates', __dir__)
6
- desc 'Generates a policy definition file based on given namespace.'
7
- argument :name, type: :string, required: true, desc: 'The namespace for the definition file.'
6
+ desc 'Generates a policy definition file based on given namespace. Leave empty for root-level definitions.'
7
+ argument :name, type: :string, required: false, default: '',
8
+ desc: 'The namespace for the definition file. Leave empty for root-level definitions.'
8
9
 
9
10
  def create_policy_definition_file
10
- return if processed_name.blank?
11
-
12
- template 'definitions.rb', "app/policies/#{processed_name}/definitions.rb"
11
+ if processed_name.blank?
12
+ # Generate root-level definitions.rb
13
+ template 'root_definitions.rb', 'app/policies/definitions.rb'
14
+ else
15
+ template 'modular_definitions.rb', "app/policies/#{processed_name}/definitions.rb"
16
+ end
13
17
  end
14
18
 
15
19
  private
16
20
 
17
21
  def processed_name
18
- @processed_name ||= name.strip.delete_prefix('/').delete_suffix('/')
22
+ @processed_name ||= name.strip.delete_prefix('/').delete_suffix('/') || ''
19
23
  end
20
24
  end
@@ -17,7 +17,7 @@ module Policies
17
17
  {
18
18
  code: 'Another-Policy-Code',
19
19
  name: 'Another Policy',
20
- description: 'Long description.',
20
+ description: 'Long description.'
21
21
  }
22
22
  ].freeze
23
23
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Policies
4
+ DEFINITIONS = [
5
+ {
6
+ # Used as identifier for the policy.
7
+ code: 'Policy-Code',
8
+ # Readable name for the policy.
9
+ name: 'Readable Policy Name',
10
+ # Readable description for the policy.
11
+ description: 'Short description of what this policy allows',
12
+ # Controller actions the policy applies to. It works as matchers based on routes.
13
+ # It can be empty for manual policy checking.
14
+ actions: ['users', 'products#index', 'dashboard']
15
+ },
16
+ {
17
+ code: 'Another-Policy-Code',
18
+ name: 'Another Policy',
19
+ description: 'Long description.'
20
+ }
21
+ ].freeze
22
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: controller_policies
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tien
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -47,7 +47,8 @@ files:
47
47
  - lib/controller_policies/railtie.rb
48
48
  - lib/controller_policies/version.rb
49
49
  - lib/generators/policy_definition_generator.rb
50
- - lib/generators/templates/definitions.rb.tt
50
+ - lib/generators/templates/modular_definitions.rb.tt
51
+ - lib/generators/templates/root_definitions.rb.tt
51
52
  homepage: https://github.com/tieeeeen1994/rails-controller-policies
52
53
  licenses:
53
54
  - MIT
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  - !ruby/object:Gem::Version
69
70
  version: '0'
70
71
  requirements: []
71
- rubygems_version: 3.6.5
72
+ rubygems_version: 3.7.1
72
73
  specification_version: 4
73
74
  summary: Allows the developer to define policies for controllers.
74
75
  test_files: []