hubbado-policy 1.1.1 → 1.2.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: 92b2d2ebfeca29045209e9ac790024cfe3be4d6646be7ace9f3678684808cad6
4
- data.tar.gz: 88b9eb114f043c6b8f948fb780446566bf4a73771aabc96a9becfb670374a701
3
+ metadata.gz: fb076b8106150620c0cbe71b07bf2cf8f3c83234d6269fdb01395726fe83833f
4
+ data.tar.gz: e40f7c0787d5aafb14a41f889093fb488d0ed0c9faaaf4dcb81cc839c6d15f96
5
5
  SHA512:
6
- metadata.gz: b28dc471aa7bcbee6db421e41cb4664423a6c58d31ab17e6deb2ae55d0a53fbf1b8c00e5a2ef96c34bd46d325fac68b5fb77fe0c88e59f0ca28eb2359affac28
7
- data.tar.gz: 29670f1bb98582426e288fd290091d20ac8bf7ee9606a61f6de6b20a869794342c7646bc630c41bea8c0af1217033612c5455ea32b04aee05c8aa082546455e7
6
+ metadata.gz: c79b6a45e34b558a77e3aaaba9caa6e6963cab5e97f7ceff932fdf1150a75c7d94386baf173508923c6e87bcc0b7e5e5f1e69325cc64ba5d878b1c052980b835
7
+ data.tar.gz: e5f01ac761f171f755196057dbfe667171bccc5165c5ebd2cef1fa73df490f3ef6a5a50aa0065d343b1440ec2e1e076911c3b0f95953d6868d2ddcbd2b8631cc
data/CHANGELOG.md CHANGED
@@ -5,14 +5,38 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.1] - 2025-06-02
9
+
10
+ ### Fixed
11
+
12
+ - Policies class instance variable is correctly copied from parent policy
13
+
14
+ ## [1.2.0] - 2025-05-27
15
+
16
+ ### Added
17
+
18
+ - Policy control is improved:
19
+ - When using a policy control you could permit or deny with a method
20
+ ```ruby
21
+ mimic_policy = Control::Policies::ArticlePolicy.example
22
+ mimic_policy.permit(:view)
23
+ # or
24
+ mimic_policy.deny(:view)
25
+ mimic_policy.deny(:view, :a_reason) # with a reason
26
+ mimic_policy.deny(:view, data: :custom_data) # with data
27
+ mimic_policy.deny(:view, :a_reason, data: :custom_data) # or both
28
+ ```
29
+
8
30
  ## [1.1.1] - 2025-05-26
9
31
 
10
32
  ### Fixed
33
+
11
34
  - Railtie is corrected with an initializer to load I18n locales correctly.
12
35
 
13
36
  ## [1.1.0] - 2025-05-20
14
37
 
15
38
  ### Added
39
+
16
40
  - Control to mimic policies
17
41
 
18
42
  ## [1.0.1] - 2025-05-20
@@ -22,6 +46,7 @@ Bump because rubygems does not allow repushing the same version even if it is ya
22
46
  ## [1.0.0] - 2025-05-20
23
47
 
24
48
  ### Added
49
+
25
50
  - Initial release of the hubbado-policy gem
26
51
  - `Policy` class with DSL for defining authorization rules
27
52
  - `Result` class to represent policy outcomes
@@ -31,6 +56,7 @@ Bump because rubygems does not allow repushing the same version even if it is ya
31
56
  - Full compatibility with the eventide-project/dependency gem
32
57
 
33
58
  ### Documentation
59
+
34
60
  - Comprehensive README with usage examples
35
61
  - Detailed explanations of all major components
36
62
  - Rails integration guide
data/README.md CHANGED
@@ -152,11 +152,24 @@ module Control
152
152
  end
153
153
 
154
154
  mimic_policy = Control::Policies::ArticlePolicy.example
155
- mimic_policy.view? # returns the value from the real policy
156
-
155
+ mimic_policy.view? # returns false (denied by default)
157
156
 
157
+ # Override specific policies
158
158
  mimic_policy = Control::Policies::ArticlePolicy.example(view: ArticlePolicy.denied)
159
159
  mimic_policy.view? # returns false
160
+
161
+ # Control policy behavior with permit/deny methods
162
+ mimic_policy = Control::Policies::ArticlePolicy.example
163
+ mimic_policy.permit(:view)
164
+ mimic_policy.view? # returns true
165
+
166
+ mimic_policy.deny(:view)
167
+ mimic_policy.view? # returns false
168
+
169
+ # Deny with reason and data
170
+ mimic_policy.deny(:view, :not_authorized)
171
+ mimic_policy.deny(:view, data: { reason: "custom data" })
172
+ mimic_policy.deny(:view, :not_authorized, data: { user_id: 123 })
160
173
  ```
161
174
 
162
175
  ## Result Objects
@@ -190,18 +203,55 @@ end
190
203
  - `message` - Returns the localized error message
191
204
  - `data` - Returns any additional context data
192
205
 
206
+ ### Error Handling
207
+
208
+ Policies validate their inputs and will raise errors for invalid usage:
209
+
210
+ ```ruby
211
+ # Will raise "User not provided" error
212
+ ArticlePolicy.build(nil, article)
213
+
214
+ # Control policies raise UnkownPolicy for invalid policy names
215
+ mimic_policy = Control::Policies::ArticlePolicy.example
216
+ mimic_policy.permit(:invalid_policy) # raises UnkownPolicy
217
+ ```
218
+
219
+ ### Accessing Result Data
220
+
221
+ When policies return additional context data, you can access it through the result:
222
+
223
+ ```ruby
224
+ # In your policy
225
+ define_policy :edit do
226
+ return denied(:quota_exceeded, data: { limit: 10, current: 12 }) if over_quota?
227
+ permitted
228
+ end
229
+
230
+ # Using the result
231
+ result = policy.edit
232
+ if result.denied?
233
+ puts result.message # "You have exceeded your quota"
234
+ puts result.data[:limit] # 10
235
+ puts result.data[:current] # 12
236
+ end
237
+ ```
238
+
193
239
  ## Scope Objects
194
240
 
195
241
  Scope objects filter collections based on what a user is authorized to access.
196
242
 
197
243
  ### Basic Usage
198
244
 
245
+ Scope objects require two template methods to be implemented in subclasses:
246
+
199
247
  ```ruby
200
248
  class ArticleScope < Hubbado::Policy::Scope
249
+ # Required: Define the base collection to filter
201
250
  def self.default_scope
202
251
  Article.all
203
252
  end
204
253
 
254
+ # Required: Implement the filtering logic
205
255
  def resolve(record, scope, **options)
206
256
  return scope if record.admin?
207
257
 
@@ -213,6 +263,14 @@ end
213
263
  visible_articles = ArticleScope.call(current_user)
214
264
  ```
215
265
 
266
+ **Required Methods:**
267
+ - `default_scope` - Class method that returns the base collection to filter
268
+ - `resolve(record, scope, **options)` - Instance method that applies filtering logic
269
+
270
+ Both methods must be implemented or a `MethodMissing` error will be raised.
271
+
272
+ **Important:** Like policies, use `Scope.call()` instead of manual instantiation to ensure the `configure` method is called if defined.
273
+
216
274
  ### Custom Scopes
217
275
 
218
276
  You can pass custom base scopes:
@@ -1,11 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "hubbado-policy"
3
- s.version = "1.1.1"
3
+ s.version = "1.2.1"
4
4
  s.summary = "A lightweight, flexible policy framework for Ruby applications"
5
5
 
6
6
  s.authors = ["Hubbado Devs"]
7
7
  s.email = ["devs@hubbado.com"]
8
- s.homepage = 'https://github.com/hubbado/hubbado-policy'
8
+ s.homepage = "https://github.com/hubbado/hubbado-policy"
9
9
  s.license = "MIT"
10
10
 
11
11
  s.metadata["homepage_uri"] = s.homepage
@@ -24,10 +24,10 @@ Gem::Specification.new do |s|
24
24
  s.platform = Gem::Platform::RUBY
25
25
  s.required_ruby_version = ">= 3.2"
26
26
 
27
- s.add_runtime_dependency "i18n"
28
27
  s.add_runtime_dependency "evt-casing"
29
28
  s.add_runtime_dependency "evt-record_invocation"
30
29
  s.add_runtime_dependency "evt-template_method"
30
+ s.add_runtime_dependency "i18n"
31
31
 
32
32
  s.add_development_dependency "debug"
33
33
  s.add_development_dependency "hubbado-style"
@@ -7,8 +7,14 @@ module Hubbado
7
7
  end
8
8
 
9
9
  module ClassMethods
10
+ def inherited(subclass)
11
+ super
12
+ # Copy policies from parent class to subclass
13
+ subclass.instance_variable_set(:@policies, @policies&.dup || Set.new)
14
+ end
15
+
10
16
  def define_policy(policy, *args, **kwargs, &block)
11
- @policies ||= []
17
+ @policies ||= Set.new
12
18
  @policies << policy
13
19
 
14
20
  # NOTE: This uses the technique described here so that the block given to
@@ -2,6 +2,8 @@ module Hubbado
2
2
  module Policy
3
3
  module Controls
4
4
  class Policy
5
+ UnkownPolicy = Class.new(StandardError)
6
+
5
7
  def self.mimic(policy_class)
6
8
  policy_class.policies.each do |policy|
7
9
  send(:attr_writer, policy)
@@ -14,6 +16,22 @@ module Hubbado
14
16
  send(policy).permitted?
15
17
  end
16
18
  end
19
+
20
+ define_method :permit do |policy|
21
+ unless policy_class.instance_variable_get("@policies").include?(policy)
22
+ raise UnkownPolicy
23
+ end
24
+
25
+ send("#{policy}=", policy_class.permitted)
26
+ end
27
+
28
+ define_method :deny do |policy, reason = nil, data: nil|
29
+ unless policy_class.instance_variable_get("@policies").include?(policy)
30
+ raise UnkownPolicy
31
+ end
32
+
33
+ send("#{policy}=", policy_class.denied(reason, data: data))
34
+ end
17
35
  end
18
36
 
19
37
  def initialize(policies = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubbado-policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hubbado Devs
@@ -10,7 +10,7 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: i18n
13
+ name: evt-casing
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
@@ -24,7 +24,7 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: evt-casing
27
+ name: evt-record_invocation
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
@@ -38,7 +38,7 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: evt-record_invocation
41
+ name: evt-template_method
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
@@ -52,7 +52,7 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: evt-template_method
55
+ name: i18n
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="