rabarber 5.2.5 → 6.0.0

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: 23cc4a1c2e65a3b450653b65df49633601756ada11844173b0c6314c41067ca9
4
- data.tar.gz: 95153a3172c58bf636cb4d17981ba70336a292368f6bac26c2b6a107c4fcbaaa
3
+ metadata.gz: b63470cbdf375117e5c901ae46992d0fb85868acedacde3ab17472437d8f4a10
4
+ data.tar.gz: bba19d48ebad8a6440c70e7555d5befcc9c64f67c0a43f0408cd966f9d24aa15
5
5
  SHA512:
6
- metadata.gz: 180407341e8e4e700fff624ab12de40d9aa66ed4e132e8ddc92c0c9cb4c010309194e7b37bc825a9a92a0e9baf64022a165788055ccba6b41e60aab8d040fc20
7
- data.tar.gz: 882d08be5cab1db66d0056ef5e0ace724d2d8ce633cdf75af808d28980faeadbdf9e56c4362e3b7197a89b0de80925f77b2dafb12c5f6e44b1f21cb43be0730d
6
+ metadata.gz: 5b43430eb2fc105add42baa08aa12349137f1aab6021d371400cc48c48c134db4c20bd81e3cbff043e63bf6f22161d6645ab57619090bf1c08b905f6be60fbe1
7
+ data.tar.gz: cb648ea4388e067d1f06d57f2ba9b45fb7139df3a5a1cbb102ae42441730a2a83749b116d468fb0c6d3b9b0be419c010a62c73c3c0be2d90752086aa5f4e8837
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## v6.0.0
2
+
3
+ ### Breaking:
4
+
5
+ - Removed methods deprecated in v5.2.0 from the public API
6
+ - Dropped support for Ruby 3.2
7
+ - Dropped support for Rails 7.1
8
+
9
+ ### Bugs:
10
+
11
+ - Fixed cache clearing not working with Memcached
12
+
13
+ ### Misc:
14
+
15
+ - Optimized cache key generation
16
+ - Dropped some unnecessary dependencies
17
+
18
+ To upgrade to v6.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/101)
19
+
1
20
  ## v5.2.5
2
21
 
3
22
  ### Misc:
data/README.md CHANGED
@@ -9,29 +9,23 @@ Rabarber is a role-based authorization library for Ruby on Rails. It provides a
9
9
 
10
10
  **Example of Usage:**
11
11
 
12
- Consider a CRM system where users with different roles have distinct access levels. For instance, the role `accountant` can access invoice data but not marketing information, while the role `analyst` can view marketing data but not detailed financial records. You can define such authorization rules easily with Rabarber.
13
-
14
- ___
15
-
16
- And this is how your controller might look with Rabarber:
12
+ Consider a CRM system where users with different roles have distinct access levels. For instance, the role `accountant` can access and manage invoice data, while the role `analyst` can only view it. Here's how you'd define that with Rabarber:
17
13
 
18
14
  ```rb
19
- class TicketsController < ApplicationController
20
- grant_access roles: :admin
15
+ class InvoicesController < ApplicationController
16
+ grant_access roles: :accountant
21
17
 
22
- grant_access action: :index, roles: :manager
18
+ grant_access action: :index, roles: :analyst
23
19
  def index
24
- # ...
20
+ # Accessible to accountant and analyst
25
21
  end
26
22
 
27
- def destroy
28
- # ...
23
+ def update
24
+ # Accessible to accountant only
29
25
  end
30
26
  end
31
27
  ```
32
28
 
33
- This means that `admin` users can access everything in `TicketsController`, while the `manager` role can access only the `index` action.
34
-
35
29
  ## Table of Contents
36
30
 
37
31
  **Gem Usage:**
@@ -87,9 +81,12 @@ Create an initializer to customize Rabarber's behavior (optional):
87
81
 
88
82
  ```rb
89
83
  Rabarber.configure do |config|
90
- config.cache_enabled = true # Enable/disable role caching (default: true)
91
- config.current_user_method = :current_user # Method to access current user (default: :current_user)
92
- config.user_model_name = "User" # User model name (default: "User")
84
+ # Enable/disable role caching (default: true)
85
+ config.cache_enabled = true
86
+ # Method to access current user (default: :current_user)
87
+ config.current_user_method = :current_user
88
+ # User model name (default: "User")
89
+ config.user_model_name = "User"
93
90
  end
94
91
  ```
95
92
 
@@ -119,7 +116,7 @@ user.revoke_roles(:admin, :manager)
119
116
  user.revoke_all_roles
120
117
  ```
121
118
 
122
- All role assignment methods return the list of roles currently assigned to the user.
119
+ All role assignment and revocation methods return the list of roles currently assigned to the user.
123
120
 
124
121
  ### Role Queries
125
122
 
@@ -139,19 +136,26 @@ User.with_role(:admin, :manager)
139
136
 
140
137
  ## Direct Role Management
141
138
 
142
- You can also manage roles directly:
139
+ You can also directly manage roles available in the application:
143
140
 
144
141
  ```rb
145
142
  # Create a new role
146
- Rabarber.create_role(:admin) # => true if created, false if already exists
143
+ Rabarber.create_role(:admin)
144
+ # => true if created, false if already exists
147
145
 
148
146
  # Rename a role
149
- Rabarber.rename_role(:admin, :administrator) # => true if renamed, false if new name exists or role is assigned
150
- Rabarber.rename_role(:admin, :administrator, force: true) # Force rename even if role is assigned
147
+ Rabarber.rename_role(:admin, :administrator)
148
+ # => true if renamed, false if new name exists or role is assigned
149
+
150
+ # Force rename even if role is assigned
151
+ Rabarber.rename_role(:admin, :administrator, force: true)
151
152
 
152
153
  # Remove a role
153
- Rabarber.delete_role(:admin) # => true if deleted, false if role is assigned
154
- Rabarber.delete_role(:admin, force: true) # Force deletion even if role is assigned
154
+ Rabarber.delete_role(:admin)
155
+ # => true if deleted, false if role is assigned
156
+
157
+ # Force deletion even if role is assigned
158
+ Rabarber.delete_role(:admin, force: true)
155
159
 
156
160
  # List available roles in the global context
157
161
  Rabarber.roles
@@ -160,19 +164,18 @@ Rabarber.roles
160
164
  Rabarber.all_roles
161
165
  ```
162
166
 
163
- > **Note:** Some methods have been deprecated in favor of the new API shown above. Deprecated methods still work but will be removed in a future major version. See the [changelog](https://github.com/enjaku4/rabarber/blob/main/CHANGELOG.md#v520) for the complete list of deprecated methods and their replacements.
164
-
165
167
  ## Authorization
166
168
 
167
169
  ### Setup
168
170
 
169
- Include `Rabarber::Authorization` module in your controllers and configure protection:
171
+ Include `Rabarber::Authorization` module in your controllers and configure authorization:
170
172
 
171
173
  ```rb
172
174
  class ApplicationController < ActionController::Base
173
175
  include Rabarber::Authorization
174
176
 
175
- with_authorization # Enable authorization check for all actions in all controllers by default
177
+ # Enable authorization check for all actions in all controllers by default
178
+ with_authorization
176
179
  end
177
180
  ```
178
181
 
@@ -180,11 +183,13 @@ You can also enable authorization checks selectively. Both `with_authorization`
180
183
 
181
184
  ```rb
182
185
  class TicketsController < ApplicationController
183
- skip_authorization only: [:index, :show] # Skip authorization for specific actions
186
+ # Skip authorization for specific actions
187
+ skip_authorization only: [:index, :show]
184
188
  end
185
189
 
186
190
  class InvoicesController < ApplicationController
187
- with_authorization except: [:index] # Enable authorization for all actions except index
191
+ # Enable authorization for all actions except index
192
+ with_authorization except: [:index]
188
193
  end
189
194
  ```
190
195
 
@@ -192,6 +197,8 @@ Authorization requires an authenticated user. Rabarber will raise an error if no
192
197
 
193
198
  ### Authorization Rules
194
199
 
200
+ Rabarber follows a deny-by-default principle: if no `grant_access` rule is defined for an action or controller, access is denied to everyone.
201
+
195
202
  Define authorization rules using `grant_access`:
196
203
 
197
204
  ```rb
@@ -211,15 +218,17 @@ class TicketsController < ApplicationController
211
218
  end
212
219
  ```
213
220
 
214
- Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action:
221
+ Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action or controller:
215
222
 
216
223
  ```rb
217
224
  class BaseController < ApplicationController
218
- grant_access roles: :admin # Admin can access everything
225
+ # Admin can access everything
226
+ grant_access roles: :admin
219
227
  end
220
228
 
221
229
  class InvoicesController < BaseController
222
- grant_access roles: :accountant # Accountant can also access InvoicesController (along with admin)
230
+ # Accountant can also access InvoicesController (along with admin)
231
+ grant_access roles: :accountant
223
232
 
224
233
  grant_access action: :index, roles: :manager
225
234
  grant_access action: :index, roles: :supervisor
@@ -233,16 +242,19 @@ It's possible to omit roles to allow unrestricted access:
233
242
 
234
243
  ```rb
235
244
  class UnrestrictedController < ApplicationController
236
- grant_access # Allow all users to access all actions
245
+ # Allow all users to access all actions
246
+ grant_access
237
247
  end
238
248
 
239
249
  class MixedController < ApplicationController
240
- grant_access action: :index # Unrestricted index action
250
+ # Unrestricted index action
251
+ grant_access action: :index
241
252
  def index
242
253
  # Accessible to all users
243
254
  end
244
255
 
245
- grant_access action: :show, roles: :member # Restricted show action
256
+ # Restricted show action
257
+ grant_access action: :show, roles: :member
246
258
  def show
247
259
  # Accessible to members only
248
260
  end
@@ -276,7 +288,7 @@ Dynamic rules can also be used without roles at all, allowing you to define cust
276
288
 
277
289
  ```rb
278
290
  class InvoicesController < ApplicationController
279
- grant_access action: :update, unless: -> { Date.current.on_weekend? }
291
+ grant_access action: :update, unless: -> { invoice.period_closed? }
280
292
  def update
281
293
  # ...
282
294
  end
@@ -289,7 +301,11 @@ class InvoicesController < ApplicationController
289
301
  private
290
302
 
291
303
  def destroy_allowed?
292
- InvoicePolicy.new(current_user).destroy?(Invoice.find(params[:id]))
304
+ InvoicePolicy.new(current_user).destroy?(invoice)
305
+ end
306
+
307
+ def invoice
308
+ @invoice ||= Invoice.find(params[:id])
293
309
  end
294
310
  end
295
311
  ```
@@ -307,7 +323,8 @@ class ApplicationController < ActionController::Base
307
323
  private
308
324
 
309
325
  def when_unauthorized
310
- head :not_found # Custom behavior to hide existence of protected resources
326
+ # Custom behavior to hide existence of protected resources
327
+ head :not_found
311
328
  end
312
329
  end
313
330
  ```
@@ -445,7 +462,7 @@ Use conditional rendering based on roles:
445
462
  ## Getting Help and Contributing
446
463
 
447
464
  ### Getting Help
448
- Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/enjaku4/rabarber/discussions) for:
465
+ Have a question or need assistance? Open a discussion in the [discussions section](https://github.com/enjaku4/rabarber/discussions) for:
449
466
  - Usage questions
450
467
  - Implementation guidance
451
468
  - Feature suggestions
@@ -460,7 +477,7 @@ Found a bug? Please [create an issue](https://github.com/enjaku4/rabarber/issues
460
477
  Ready to contribute? You can:
461
478
  - Fix bugs by submitting pull requests
462
479
  - Improve documentation
463
- - Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/rabarber/discussions))
480
+ - Add new features (please discuss first in the [discussions section](https://github.com/enjaku4/rabarber/discussions))
464
481
 
465
482
  Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md)
466
483
 
@@ -476,5 +493,5 @@ Everyone interacting in the Rabarber project is expected to follow the [code of
476
493
 
477
494
  Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
478
495
 
479
- [v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
496
+ [v5.x.x](https://github.com/enjaku4/rabarber/blob/12a23858e974f5cc0a4ebddfc18bdf84171dd554/README.md) | [v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
480
497
  | [v2.x.x](https://github.com/enjaku4/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/enjaku4/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
@@ -1,43 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry-configurable"
3
+ require "singleton"
4
4
 
5
5
  module Rabarber
6
- module Configuration
7
- extend Dry::Configurable
8
-
9
- module_function
10
-
11
- setting :cache_enabled,
12
- default: true,
13
- reader: true,
14
- constructor: -> (value) do
15
- Rabarber::Inputs::Boolean.new(
16
- value,
17
- error: Rabarber::ConfigurationError,
18
- message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
19
- ).process
20
- end
21
- setting :current_user_method,
22
- default: :current_user,
23
- reader: true,
24
- constructor: -> (value) do
25
- Rabarber::Inputs::Symbol.new(
26
- value,
27
- error: Rabarber::ConfigurationError,
28
- message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
29
- ).process
30
- end
31
- setting :user_model_name,
32
- default: "User",
33
- reader: true,
34
- constructor: -> (value) do
35
- Rabarber::Inputs::NonEmptyString.new(
36
- value,
37
- error: Rabarber::ConfigurationError,
38
- message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
39
- ).process
40
- end
6
+ class Configuration
7
+ include Singleton
8
+
9
+ attr_reader :cache_enabled, :current_user_method, :user_model_name
10
+
11
+ def initialize
12
+ reset_to_defaults!
13
+ end
14
+
15
+ def cache_enabled=(value)
16
+ @cache_enabled = Rabarber::Inputs::Boolean.new(
17
+ value,
18
+ error: Rabarber::ConfigurationError,
19
+ message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
20
+ ).process
21
+ end
22
+
23
+ def current_user_method=(value)
24
+ @current_user_method = Rabarber::Inputs::NonEmptySymbol.new(
25
+ value,
26
+ error: Rabarber::ConfigurationError,
27
+ message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
28
+ ).process
29
+ end
30
+
31
+ def user_model_name=(value)
32
+ @user_model_name = Rabarber::Inputs::NonEmptyString.new(
33
+ value,
34
+ error: Rabarber::ConfigurationError,
35
+ message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
36
+ ).process
37
+ end
38
+
39
+ def reset_to_defaults!
40
+ self.cache_enabled = true
41
+ self.current_user_method = :current_user
42
+ self.user_model_name = "User"
43
+ end
41
44
 
42
45
  def user_model
43
46
  Rabarber::Inputs::Model.new(
@@ -46,5 +49,16 @@ module Rabarber
46
49
  message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{user_model_name.inspect}"
47
50
  ).process
48
51
  end
52
+
53
+ def configure
54
+ yield self
55
+ end
56
+
57
+ class << self
58
+ delegate :cache_enabled, :current_user_method, :user_model_name,
59
+ :cache_enabled=, :current_user_method=, :user_model_name=,
60
+ :user_model, :reset_to_defaults!, :configure,
61
+ to: :instance
62
+ end
49
63
  end
50
64
  end
@@ -24,7 +24,7 @@ module Rabarber
24
24
 
25
25
  Rabarber::Core::Permissions.add(
26
26
  self,
27
- Rabarber::Inputs::Symbol.new(
27
+ Rabarber::Inputs::NonEmptySymbol.new(
28
28
  action,
29
29
  optional: true,
30
30
  message: "Expected a symbol or a string, got #{action.inspect}"
@@ -1,38 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest/sha2"
3
+ require "digest/md5"
4
+ require "securerandom"
4
5
 
5
6
  module Rabarber
6
7
  module Core
7
8
  module Cache
8
- module_function
9
+ extend self
9
10
 
10
- def fetch(uid, &)
11
+ def fetch(roleable_id, scope, &)
11
12
  return yield unless enabled?
12
13
 
13
- Rails.cache.fetch(prepare_key(uid), expires_in: 1.hour, race_condition_ttl: 5.seconds, &)
14
+ Rails.cache.fetch(prepare_key(roleable_id, scope), expires_in: 1.hour, race_condition_ttl: 5.seconds, &)
14
15
  end
15
16
 
16
- def delete(*uids)
17
+ def delete(*pairs)
17
18
  return unless enabled?
18
19
 
19
- Rails.cache.delete_multi(uids.map { prepare_key(_1) }) if uids.any?
20
+ Rails.cache.delete_multi(pairs.map { |roleable_id, scope| prepare_key(roleable_id, scope) }) if pairs.any?
20
21
  end
21
22
 
23
+ def clear
24
+ Rails.cache.write(VERSION_KEY, SecureRandom.alphanumeric(8))
25
+ end
26
+
27
+ private
28
+
22
29
  def enabled?
23
30
  Rabarber::Configuration.cache_enabled
24
31
  end
25
32
 
26
- def clear
27
- Rails.cache.delete_matched(/^#{CACHE_PREFIX}/o)
33
+ def prepare_key(roleable_id, scope)
34
+ Digest::MD5.base64digest(Marshal.dump([current_version, roleable_id, scope]))
28
35
  end
29
36
 
30
- def prepare_key(uid)
31
- "#{CACHE_PREFIX}:#{Digest::SHA2.hexdigest(Marshal.dump(uid))}"
37
+ def current_version
38
+ version = Rails.cache.read(VERSION_KEY).presence
39
+
40
+ return version if version
41
+
42
+ clear
43
+
44
+ Rails.cache.read(VERSION_KEY)
32
45
  end
33
46
 
34
- CACHE_PREFIX = "rabarber"
35
- private_constant :CACHE_PREFIX
47
+ VERSION_KEY = "rabarber"
48
+ private_constant :VERSION_KEY
36
49
  end
37
50
  end
38
51
 
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry-types"
4
-
5
3
  module Rabarber
6
4
  module Inputs
7
5
  class Base
8
- include Dry.Types()
9
-
10
6
  def initialize(value, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
11
7
  @value = value
12
8
  @optional = optional
@@ -15,15 +11,14 @@ module Rabarber
15
11
  end
16
12
 
17
13
  def process
18
- type_checker = @optional ? type.optional : type
19
- type_checker[@value]
20
- rescue Dry::Types::CoercionError
21
- raise_error
14
+ return @value if @value.nil? && @optional
15
+
16
+ validate_and_normalize
22
17
  end
23
18
 
24
19
  private
25
20
 
26
- def type
21
+ def validate_and_normalize
27
22
  raise NotImplementedError
28
23
  end
29
24
 
@@ -5,7 +5,7 @@ module Rabarber
5
5
  class Boolean < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type = self.class::Strict::Bool
8
+ def validate_and_normalize = @value == true || @value == false ? @value : raise_error
9
9
  end
10
10
  end
11
11
  end
@@ -19,14 +19,13 @@ module Rabarber
19
19
 
20
20
  private
21
21
 
22
- def type
23
- self.class::Strict::Class |
24
- self.class::Instance(ActiveRecord::Base) |
25
- self.class::Hash.schema(
26
- context_type: self.class::Strict::String | self.class::Nil,
27
- context_id: self.class::Strict::String | self.class::Strict::Integer | self.class::Nil
28
- ) |
29
- self.class::Nil
22
+ def validate_and_normalize
23
+ return @value if @value.nil?
24
+ return @value if @value.is_a?(Class)
25
+ return @value if @value.is_a?(ActiveRecord::Base)
26
+ return @value if @value in { context_type: String | nil, context_id: String | Integer | nil }
27
+
28
+ raise_error
30
29
  end
31
30
  end
32
31
  end
@@ -4,9 +4,21 @@ module Rabarber
4
4
  module Inputs
5
5
  module Contexts
6
6
  class Authorizational < Rabarber::Inputs::Context
7
+ def resolve
8
+ result = process
9
+ return result if result.is_a?(Symbol) || result.is_a?(Proc)
10
+
11
+ super
12
+ end
13
+
7
14
  private
8
15
 
9
- def type = self.class::Coercible::Symbol.constrained(min_size: 1) | self.class::Instance(Proc) | super
16
+ def validate_and_normalize
17
+ return @value if @value.is_a?(Proc)
18
+ return @value.to_sym if (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.present?
19
+
20
+ super
21
+ end
10
22
  end
11
23
  end
12
24
  end
@@ -5,7 +5,11 @@ module Rabarber
5
5
  class DynamicRule < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type = self.class::Coercible::Symbol.constrained(min_size: 1) | self.class::Instance(Proc)
8
+ def validate_and_normalize
9
+ return @value if @value.is_a?(Proc)
10
+
11
+ Rabarber::Inputs::NonEmptySymbol.new(@value, error: @error, message: @message).process
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -5,7 +5,12 @@ module Rabarber
5
5
  class Model < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type = self.class::Strict::Class.constructor { _1.try(:safe_constantize) }.constrained(lt: ActiveRecord::Base)
8
+ def validate_and_normalize
9
+ model = @value.try(:safe_constantize)
10
+ raise_error unless model.is_a?(Class) && model < ActiveRecord::Base
11
+
12
+ model
13
+ end
9
14
  end
10
15
  end
11
16
  end
@@ -5,7 +5,9 @@ module Rabarber
5
5
  class NonEmptyString < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type = self.class::Strict::String.constrained(min_size: 1)
8
+ def validate_and_normalize
9
+ @value.is_a?(String) && @value.present? ? @value : raise_error
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class NonEmptySymbol < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def validate_and_normalize
9
+ raise_error unless (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.present?
10
+
11
+ @value.to_sym
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,7 +5,11 @@ module Rabarber
5
5
  class Role < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type = self.class::Coercible::Symbol.constrained(min_size: 1, format: /\A[a-z0-9_]+\z/)
8
+ def validate_and_normalize
9
+ raise_error unless (@value.is_a?(String) || @value.is_a?(Symbol)) && @value.to_s.match?(/\A[a-z0-9_]+\z/)
10
+
11
+ @value.to_sym
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -5,8 +5,10 @@ module Rabarber
5
5
  class Roles < Rabarber::Inputs::Base
6
6
  private
7
7
 
8
- def type
9
- self.class::Array.of(self.class::Coercible::Symbol.constrained(min_size: 1, format: /\A[a-z0-9_]+\z/)).constructor { Kernel::Array(_1) }
8
+ def validate_and_normalize
9
+ Array(@value).map do |role|
10
+ Rabarber::Inputs::Role.new(role, error: @error, message: @message).process
11
+ end
10
12
  end
11
13
  end
12
14
  end
@@ -40,11 +40,11 @@ module Rabarber
40
40
 
41
41
  def roles(context: nil)
42
42
  processed_context = process_context(context)
43
- Rabarber::Core::Cache.fetch([roleable_id, processed_context]) { rabarber_roles.names(context: processed_context) }
43
+ Rabarber::Core::Cache.fetch(roleable_id, processed_context) { rabarber_roles.list(context: processed_context) }
44
44
  end
45
45
 
46
46
  def all_roles
47
- Rabarber::Core::Cache.fetch([roleable_id, :all]) { rabarber_roles.all_names }
47
+ Rabarber::Core::Cache.fetch(roleable_id, :all) { rabarber_roles.list_all }
48
48
  end
49
49
 
50
50
  def has_role?(*role_names, context: nil)
@@ -60,7 +60,7 @@ module Rabarber
60
60
  create_new_roles(processed_role_names, context: processed_context) if create_new
61
61
 
62
62
  roles_to_assign = Rabarber::Role.where(
63
- name: (processed_role_names - rabarber_roles.names(context: processed_context)), **processed_context
63
+ name: (processed_role_names - rabarber_roles.list(context: processed_context)), **processed_context
64
64
  )
65
65
 
66
66
  if roles_to_assign.any?
@@ -76,7 +76,7 @@ module Rabarber
76
76
  processed_context = process_context(context)
77
77
 
78
78
  roles_to_revoke = Rabarber::Role.where(
79
- name: processed_role_names.intersection(rabarber_roles.names(context: processed_context)), **processed_context
79
+ name: processed_role_names.intersection(rabarber_roles.list(context: processed_context)), **processed_context
80
80
  )
81
81
 
82
82
  if roles_to_revoke.any?
@@ -11,15 +11,11 @@ module Rabarber
11
11
  join_table: "rabarber_roles_roleables"
12
12
 
13
13
  class << self
14
- def names(context: nil)
15
- deprecation_warning("names", "Rabarber.roles")
16
-
14
+ def list(context: nil)
17
15
  where(process_context(context)).pluck(:name).map(&:to_sym)
18
16
  end
19
17
 
20
- def all_names
21
- deprecation_warning("all_names", "Rabarber.all_roles")
22
-
18
+ def list_all
23
19
  includes(:context).each_with_object({}) do |role, hash|
24
20
  (hash[role.context] ||= []) << role.name.to_sym
25
21
  rescue ActiveRecord::RecordNotFound
@@ -29,9 +25,7 @@ module Rabarber
29
25
  raise Rabarber::NotFoundError, "Context not found: class #{e.name} may have been renamed or deleted"
30
26
  end
31
27
 
32
- def add(name, context: nil)
33
- deprecation_warning("add", "Rabarber.create_role")
34
-
28
+ def register(name, context: nil)
35
29
  name = process_role_name(name)
36
30
  processed_context = process_context(context)
37
31
 
@@ -40,9 +34,7 @@ module Rabarber
40
34
  !!create!(name:, **processed_context)
41
35
  end
42
36
 
43
- def rename(old_name, new_name, context: nil, force: false)
44
- deprecation_warning("rename", "Rabarber.rename_role")
45
-
37
+ def amend(old_name, new_name, context: nil, force: false)
46
38
  processed_context = process_context(context)
47
39
  role = find_by(name: process_role_name(old_name), **processed_context)
48
40
 
@@ -57,9 +49,7 @@ module Rabarber
57
49
  role.update!(name:)
58
50
  end
59
51
 
60
- def remove(name, context: nil, force: false)
61
- deprecation_warning("remove", "Rabarber.delete_role")
62
-
52
+ def drop(name, context: nil, force: false)
63
53
  processed_context = process_context(context)
64
54
  role = find_by(name: process_role_name(name), **processed_context)
65
55
 
@@ -72,12 +62,6 @@ module Rabarber
72
62
  !!role.destroy!
73
63
  end
74
64
 
75
- def assignees(name, context: nil)
76
- deprecation_warning("assignees", "#{Rabarber::Configuration.user_model_name}.with_role")
77
-
78
- find_by(name: process_role_name(name), **process_context(context))&.roleables || Rabarber::Configuration.user_model.none
79
- end
80
-
81
65
  def prune
82
66
  orphaned_roles = where.not(context_id: nil).includes(:context).filter_map do |role|
83
67
  !role.context
@@ -99,14 +83,6 @@ module Rabarber
99
83
 
100
84
  private
101
85
 
102
- def deprecation_warning(method, alternative)
103
- callers = caller_locations.map(&:label)
104
-
105
- return if callers.include?(alternative) || callers.include?("ActiveRecord::Relation#scoping")
106
-
107
- ActiveSupport::Deprecation.new("6.0.0", "rabarber").warn("`Rabarber::Role.#{method}` method is deprecated and will be removed in the next major version, use `#{alternative}` instead.")
108
- end
109
-
110
86
  def delete_roleables_cache(role, context:)
111
87
  Rabarber::Core::Cache.delete(*role.roleables.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
112
88
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module RoleManagement
5
+ def roles(context: nil) = Rabarber::Role.list(context:)
6
+ def all_roles = Rabarber::Role.list_all
7
+ def create_role(name, context: nil) = Rabarber::Role.register(name, context:)
8
+ def rename_role(old_name, new_name, context: nil, force: false) = Rabarber::Role.amend(old_name, new_name, context:, force:)
9
+ def delete_role(name, context: nil, force: false) = Rabarber::Role.drop(name, context:, force:)
10
+ def prune = Rabarber::Role.prune # rubocop:disable Rails/Delegate
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "5.2.5"
4
+ VERSION = "6.0.0"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -14,7 +14,7 @@ require_relative "rabarber/inputs/model"
14
14
  require_relative "rabarber/inputs/non_empty_string"
15
15
  require_relative "rabarber/inputs/role"
16
16
  require_relative "rabarber/inputs/roles"
17
- require_relative "rabarber/inputs/symbol"
17
+ require_relative "rabarber/inputs/non_empty_symbol"
18
18
 
19
19
  require_relative "rabarber/configuration"
20
20
 
@@ -27,15 +27,6 @@ module Rabarber
27
27
 
28
28
  delegate :configure, to: Rabarber::Configuration
29
29
  module_function :configure
30
-
31
- class << self
32
- def roles(context: nil) = Rabarber::Role.names(context:)
33
- def all_roles = Rabarber::Role.all_names
34
- def create_role(name, context: nil) = Rabarber::Role.add(name, context:)
35
- def rename_role(old_name, new_name, context: nil, force: false) = Rabarber::Role.rename(old_name, new_name, context:, force:)
36
- def delete_role(name, context: nil, force: false) = Rabarber::Role.remove(name, context:, force:)
37
- def prune = Rabarber::Role.prune
38
- end
39
30
  end
40
31
 
41
32
  require_relative "rabarber/core/cache"
@@ -47,6 +38,9 @@ require_relative "rabarber/helpers/helpers"
47
38
  require_relative "rabarber/helpers/migration_helpers"
48
39
  require_relative "rabarber/models/concerns/roleable"
49
40
  require_relative "rabarber/models/role"
41
+ require_relative "rabarber/models/role_management"
42
+
43
+ Rabarber.extend Rabarber::RoleManagement
50
44
 
51
45
  require_relative "rabarber/core/permissions"
52
46
 
data/rabarber.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.summary = "Simple role-based authorization library for Ruby on Rails"
20
20
  spec.description = "Simple role-based authorization for Rails applications with multi-tenancy support"
21
21
  spec.license = "MIT"
22
- spec.required_ruby_version = ">= 3.2", "< 4.1"
22
+ spec.required_ruby_version = ">= 3.3", "< 4.1"
23
23
 
24
24
  spec.files = [
25
25
  "rabarber.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
@@ -27,7 +27,5 @@ Gem::Specification.new do |spec|
27
27
 
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "dry-configurable", "~> 1.1"
31
- spec.add_dependency "dry-types", "~> 1.7"
32
- spec.add_dependency "rails", ">= 7.1", "< 8.2"
30
+ spec.add_dependency "rails", ">= 7.2", "< 8.2"
33
31
  end
metadata CHANGED
@@ -1,50 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabarber
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.5
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
8
8
  - trafium
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: dry-configurable
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.1'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.1'
27
- - !ruby/object:Gem::Dependency
28
- name: dry-types
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.7'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.7'
41
13
  - !ruby/object:Gem::Dependency
42
14
  name: rails
43
15
  requirement: !ruby/object:Gem::Requirement
44
16
  requirements:
45
17
  - - ">="
46
18
  - !ruby/object:Gem::Version
47
- version: '7.1'
19
+ version: '7.2'
48
20
  - - "<"
49
21
  - !ruby/object:Gem::Version
50
22
  version: '8.2'
@@ -54,7 +26,7 @@ dependencies:
54
26
  requirements:
55
27
  - - ">="
56
28
  - !ruby/object:Gem::Version
57
- version: '7.1'
29
+ version: '7.2'
58
30
  - - "<"
59
31
  - !ruby/object:Gem::Version
60
32
  version: '8.2'
@@ -88,11 +60,12 @@ files:
88
60
  - lib/rabarber/inputs/dynamic_rule.rb
89
61
  - lib/rabarber/inputs/model.rb
90
62
  - lib/rabarber/inputs/non_empty_string.rb
63
+ - lib/rabarber/inputs/non_empty_symbol.rb
91
64
  - lib/rabarber/inputs/role.rb
92
65
  - lib/rabarber/inputs/roles.rb
93
- - lib/rabarber/inputs/symbol.rb
94
66
  - lib/rabarber/models/concerns/roleable.rb
95
67
  - lib/rabarber/models/role.rb
68
+ - lib/rabarber/models/role_management.rb
96
69
  - lib/rabarber/railtie.rb
97
70
  - lib/rabarber/version.rb
98
71
  - rabarber.gemspec
@@ -115,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
88
  requirements:
116
89
  - - ">="
117
90
  - !ruby/object:Gem::Version
118
- version: '3.2'
91
+ version: '3.3'
119
92
  - - "<"
120
93
  - !ruby/object:Gem::Version
121
94
  version: '4.1'
@@ -125,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
98
  - !ruby/object:Gem::Version
126
99
  version: '0'
127
100
  requirements: []
128
- rubygems_version: 3.7.2
101
+ rubygems_version: 4.0.8
129
102
  specification_version: 4
130
103
  summary: Simple role-based authorization library for Ruby on Rails
131
104
  test_files: []
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rabarber
4
- module Inputs
5
- class Symbol < Rabarber::Inputs::Base
6
- private
7
-
8
- def type = self.class::Coercible::Symbol.constrained(min_size: 1)
9
- end
10
- end
11
- end