rabarber 5.2.4 → 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: 0b6af1a1a593e84b481d92883ca7adf385e2ee1fc9a3fb5615818169fc595444
4
- data.tar.gz: 97738fdc3bb6e3463b8e331088982a4dcd40e62e4251977ee63cc8b742fcb96a
3
+ metadata.gz: b63470cbdf375117e5c901ae46992d0fb85868acedacde3ab17472437d8f4a10
4
+ data.tar.gz: bba19d48ebad8a6440c70e7555d5befcc9c64f67c0a43f0408cd966f9d24aa15
5
5
  SHA512:
6
- metadata.gz: 535dc707a59522461ad3b5bd26d0e394008454c738ff52e84fa6cd7ebfdd26bb441405326563b2ba4a2229008d0ddcf3b6b426dc9d21186896e53aadf99b5fa6
7
- data.tar.gz: b32d8c3983deb895e7cefe2e3d273cbad0542477c1bad134a7681ac5ae1e390725f7e34b964de4c0f264fcb1b0d21546cb70b7d89ca29cb3c513e831d281c89c
6
+ metadata.gz: 5b43430eb2fc105add42baa08aa12349137f1aab6021d371400cc48c48c134db4c20bd81e3cbff043e63bf6f22161d6645ab57619090bf1c08b905f6be60fbe1
7
+ data.tar.gz: cb648ea4388e067d1f06d57f2ba9b45fb7139df3a5a1cbb102ae42441730a2a83749b116d468fb0c6d3b9b0be419c010a62c73c3c0be2d90752086aa5f4e8837
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
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
+
20
+ ## v5.2.5
21
+
22
+ ### Misc:
23
+
24
+ - Added support for Ruby 4
25
+
1
26
  ## v5.2.4
2
27
 
3
28
  ### Misc:
@@ -91,7 +116,7 @@ To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/e
91
116
 
92
117
  - Optimized various parts of the code and database queries for improved performance
93
118
  - Streamlined the authorization process by requiring the user to be authenticated before access is verified
94
- - Rabarber now skips roles with missing instance context and prunes them automatically; missing class context still raises errors
119
+ - Rabarber now skips roles with missing instance context; missing class context still raises errors
95
120
 
96
121
  ## v4.1.4
97
122
 
data/README.md CHANGED
@@ -5,35 +5,23 @@
5
5
  [![Github Actions badge](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/github/license/enjaku4/rabarber.svg)](LICENSE)
7
7
 
8
- Rabarber is a role-based authorization library for Ruby on Rails that focuses on controller-level access control. Rather than answering domain questions like "can this user create a post?", Rabarber answers "can this user access the create post endpoint?", providing a clean separation between authorization and business logic. It supports multi-tenancy through contextual roles, dynamic authorization with conditional logic, and includes view helpers for role-based content rendering.
8
+ Rabarber is a role-based authorization library for Ruby on Rails. It provides a set of tools for managing user roles and defining access rules, with support for multi-tenancy through context.
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 interact with invoices but cannot access marketing information, while the role `analyst` has access to marketing-related data. You can define such authorization rules easily with Rabarber.
13
-
14
- And here's how your controller might look:
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:
15
13
 
16
14
  ```rb
17
15
  class InvoicesController < ApplicationController
18
- grant_access roles: :admin # Admin can access everything
16
+ grant_access roles: :accountant
19
17
 
20
- grant_access action: :index, roles: [:accountant, :analyst]
18
+ grant_access action: :index, roles: :analyst
21
19
  def index
22
- # Accessible to both analysts and accountants
23
- end
24
-
25
- grant_access action: :show, roles: :accountant
26
- def show
27
- # Accessible to accountants
20
+ # Accessible to accountant and analyst
28
21
  end
29
22
 
30
- grant_access action: :analytics, roles: :analyst
31
- def analytics
32
- # Accessible to analysts
33
- end
34
-
35
- def destroy
36
- # Accessible to admins only
23
+ def update
24
+ # Accessible to accountant only
37
25
  end
38
26
  end
39
27
  ```
@@ -44,10 +32,11 @@ end
44
32
  - [Installation](#installation)
45
33
  - [Configuration](#configuration)
46
34
  - [User Role Methods](#user-role-methods)
47
- - [Role Management](#role-management)
48
- - [Controller Authorization](#controller-authorization)
49
- - [Dynamic Rules](#dynamic-rules)
50
- - [Multi-tenancy / Context](#multi-tenancy--context)
35
+ - [Direct Role Management](#direct-role-management)
36
+ - [Authorization](#authorization)
37
+ - [Dynamic Authorization Rules](#dynamic-authorization-rules)
38
+ - [When Unauthorized](#when-unauthorized)
39
+ - [Context / Multi-tenancy](#context--multi-tenancy)
51
40
  - [View Helpers](#view-helpers)
52
41
 
53
42
  **Community Resources:**
@@ -70,7 +59,7 @@ Install the gem:
70
59
  bundle install
71
60
  ```
72
61
 
73
- Generate the migration for role storage (replace `users` with your user table name if different):
62
+ Generate the migration to store roles (replace `users` with your table name if different):
74
63
 
75
64
  ```shell
76
65
  # For standard integer IDs
@@ -88,17 +77,20 @@ rails db:migrate
88
77
 
89
78
  ## Configuration
90
79
 
91
- Configure Rabarber in an initializer if customization is needed:
80
+ Create an initializer to customize Rabarber's behavior (optional):
92
81
 
93
82
  ```rb
94
83
  Rabarber.configure do |config|
95
- config.cache_enabled = true # Enable/disable role caching (default: true)
96
- config.current_user_method = :current_user # Method to access current user (default: :current_user)
97
- 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"
98
90
  end
99
91
  ```
100
92
 
101
- Roles are cached by default for performance. To clear the role cache manually:
93
+ Roles are cached by default for better performance. Clear the cache manually when needed:
102
94
 
103
95
  ```rb
104
96
  Rabarber::Cache.clear
@@ -112,19 +104,19 @@ Your user model is automatically augmented with role management methods:
112
104
 
113
105
  ```rb
114
106
  # Assign roles (creates roles if they don't exist)
115
- user.assign_roles(:accountant, :manager)
107
+ user.assign_roles(:admin, :manager)
116
108
 
117
- # Assign only existing roles
109
+ # Assign only existing roles (don't create new ones)
118
110
  user.assign_roles(:accountant, :manager, create_new: false)
119
111
 
120
112
  # Revoke specific roles
121
- user.revoke_roles(:accountant, :manager)
113
+ user.revoke_roles(:admin, :manager)
122
114
 
123
115
  # Revoke all roles
124
116
  user.revoke_all_roles
125
117
  ```
126
118
 
127
- 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.
128
120
 
129
121
  ### Role Queries
130
122
 
@@ -142,21 +134,28 @@ user.all_roles
142
134
  User.with_role(:admin, :manager)
143
135
  ```
144
136
 
145
- ## Role Management
137
+ ## Direct Role Management
146
138
 
147
- ### Direct Role Operations
139
+ You can also directly manage roles available in the application:
148
140
 
149
141
  ```rb
150
142
  # Create a new role
151
- Rabarber.create_role(:admin) # => true if created, false if already exists
143
+ Rabarber.create_role(:admin)
144
+ # => true if created, false if already exists
152
145
 
153
146
  # Rename a role
154
- Rabarber.rename_role(:admin, :administrator) # => true if renamed, false if new name exists or role is assigned
155
- 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)
156
152
 
157
153
  # Remove a role
158
- Rabarber.delete_role(:admin) # => true if deleted, false if role is assigned
159
- 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)
160
159
 
161
160
  # List available roles in the global context
162
161
  Rabarber.roles
@@ -165,41 +164,42 @@ Rabarber.roles
165
164
  Rabarber.all_roles
166
165
  ```
167
166
 
168
- > **Note:** Some methods have been deprecated in favor of the new API shown above. The 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 new counterparts.
167
+ ## Authorization
169
168
 
170
- ## Controller Authorization
169
+ ### Setup
171
170
 
172
- ### Basic Setup
173
-
174
- Include the authorization module and configure protection:
171
+ Include `Rabarber::Authorization` module in your controllers and configure authorization:
175
172
 
176
173
  ```rb
177
174
  class ApplicationController < ActionController::Base
178
175
  include Rabarber::Authorization
179
176
 
180
- with_authorization # Enable authorization for all actions
181
- end
182
-
183
- class InvoicesController < ApplicationController
184
- with_authorization only: [:update, :destroy] # Selective authorization
177
+ # Enable authorization check for all actions in all controllers by default
178
+ with_authorization
185
179
  end
186
180
  ```
187
181
 
188
- Authorization requires an authenticated user to be present.
189
-
190
- ### Skip Authorization
191
-
192
- You can also selectively skip authorization:
182
+ You can also enable authorization checks selectively. Both `with_authorization` and `skip_authorization` work exactly the same as Rails' `before_action` and `skip_before_action` methods:
193
183
 
194
184
  ```rb
195
185
  class TicketsController < ApplicationController
196
- skip_authorization except: [:create, :update, :destroy]
186
+ # Skip authorization for specific actions
187
+ skip_authorization only: [:index, :show]
188
+ end
189
+
190
+ class InvoicesController < ApplicationController
191
+ # Enable authorization for all actions except index
192
+ with_authorization except: [:index]
197
193
  end
198
194
  ```
199
195
 
196
+ Authorization requires an authenticated user. Rabarber will raise an error if no user is found via the configured `current_user_method`. Ensure authentication happens before authorization.
197
+
200
198
  ### Authorization Rules
201
199
 
202
- Define access rules using `grant_access`:
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
+
202
+ Define authorization rules using `grant_access`:
203
203
 
204
204
  ```rb
205
205
  class TicketsController < ApplicationController
@@ -218,17 +218,17 @@ class TicketsController < ApplicationController
218
218
  end
219
219
  ```
220
220
 
221
- ### Additive Rules
222
-
223
- Rules are additive across inheritance chains and for the same actions:
221
+ Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action or controller:
224
222
 
225
223
  ```rb
226
224
  class BaseController < ApplicationController
227
- grant_access roles: :admin # Admin can access everything
225
+ # Admin can access everything
226
+ grant_access roles: :admin
228
227
  end
229
228
 
230
229
  class InvoicesController < BaseController
231
- 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
232
232
 
233
233
  grant_access action: :index, roles: :manager
234
234
  grant_access action: :index, roles: :supervisor
@@ -238,86 +238,106 @@ class InvoicesController < BaseController
238
238
  end
239
239
  ```
240
240
 
241
- ### Unrestricted Access
242
-
243
- Omit roles to allow unrestricted access:
241
+ It's possible to omit roles to allow unrestricted access:
244
242
 
245
243
  ```rb
246
244
  class UnrestrictedController < ApplicationController
247
- grant_access # Allow all users to access all actions
245
+ # Allow all users to access all actions
246
+ grant_access
248
247
  end
249
248
 
250
249
  class MixedController < ApplicationController
251
- grant_access action: :index # Unrestricted index action
250
+ # Unrestricted index action
251
+ grant_access action: :index
252
252
  def index
253
253
  # Accessible to all users
254
254
  end
255
255
 
256
- grant_access action: :show, roles: :member # Restricted show action
256
+ # Restricted show action
257
+ grant_access action: :show, roles: :member
257
258
  def show
258
259
  # Accessible to members only
259
260
  end
260
261
  end
261
262
  ```
262
263
 
263
- ### Custom Unauthorized Handling
264
+ ## Dynamic Authorization Rules
264
265
 
265
- Override `when_unauthorized` method to customize unauthorized access behavior:
266
+ For more complex scenarios, Rabarber supports dynamic authorization rules:
266
267
 
267
268
  ```rb
268
- class ApplicationController < ActionController::Base
269
- include Rabarber::Authorization
269
+ class OrdersController < ApplicationController
270
+ grant_access roles: :manager, unless: -> { current_user.suspended? }
270
271
 
271
- with_authorization
272
+ grant_access action: :show, roles: :client, if: :user_company_matches_order?
273
+ def show
274
+ # ...
275
+ end
272
276
 
273
277
  private
274
278
 
275
- def when_unauthorized
276
- head :not_found # Custom behavior to hide existence of protected resources
279
+ def user_company_matches_order?
280
+ current_user.company == Order.find(params[:id]).company
277
281
  end
278
282
  end
279
283
  ```
280
284
 
281
- By default, when unauthorized, Rabarber will redirect back (HTML format) or return 403 (other formats).
282
-
283
- ## Dynamic Rules
285
+ You can pass a dynamic rule as an `if` or `unless` argument, which can be a symbol or a proc. Symbols refer to instance methods, and procs are evaluated in the controller at request time.
284
286
 
285
- Add conditional logic to authorization rules:
287
+ Dynamic rules can also be used without roles at all, allowing you to define custom logic or even delegate to custom policy objects:
286
288
 
287
289
  ```rb
288
- class OrdersController < ApplicationController
289
- # Method-based conditions
290
- grant_access roles: :manager, if: :company_manager?, unless: :suspended?
291
-
292
- # Proc-based conditions
293
- grant_access action: :show, roles: :client, if: -> { current_user.company == Order.find(params[:id]).company }
294
- def show
295
- # Accessible to company managers unless suspended, and to clients if the client's company matches the order's company
290
+ class InvoicesController < ApplicationController
291
+ grant_access action: :update, unless: -> { invoice.period_closed? }
292
+ def update
293
+ # ...
296
294
  end
297
295
 
298
- # Dynamic-only rules (no roles required, can be used with custom policies)
299
- grant_access action: :index, if: -> { OrdersPolicy.new(current_user).index? }
300
- def index
301
- # Accessible to company managers unless suspended, and to users based on custom policy logic
296
+ grant_access action: :destroy, if: :destroy_allowed?
297
+ def destroy
298
+ # ...
302
299
  end
303
300
 
304
301
  private
305
302
 
306
- def company_manager?
307
- current_user.manager_of?(Company.find(params[:company_id]))
303
+ def destroy_allowed?
304
+ InvoicePolicy.new(current_user).destroy?(invoice)
308
305
  end
309
306
 
310
- def suspended?
311
- current_user.suspended?
307
+ def invoice
308
+ @invoice ||= Invoice.find(params[:id])
312
309
  end
313
310
  end
314
311
  ```
315
312
 
316
- ## Multi-tenancy / Context
313
+ ## When Unauthorized
317
314
 
318
- All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes. By default, context is `nil`, meaning roles are global.
315
+ By default, when unauthorized, Rabarber will redirect back (HTML format) or return 403 (other formats). You can override `when_unauthorized` method to customize unauthorized access behavior:
319
316
 
320
- ### Contextual Role Assignment
317
+ ```rb
318
+ class ApplicationController < ActionController::Base
319
+ include Rabarber::Authorization
320
+
321
+ with_authorization
322
+
323
+ private
324
+
325
+ def when_unauthorized
326
+ # Custom behavior to hide existence of protected resources
327
+ head :not_found
328
+ end
329
+ end
330
+ ```
331
+
332
+ The `when_unauthorized` method can be overridden in any controller to provide controller-specific unauthorized access handling.
333
+
334
+ ## Context / Multi-tenancy
335
+
336
+ Rabarber supports multi-tenancy through its context feature. All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes. By default, context is `nil`, meaning roles are global. Context can also be an instance of an `ActiveRecord` model or a class.
337
+
338
+ For example, in a project management app, you might want users to have different roles in different projects - someone could be an `owner` in one project but just a `member` in another.
339
+
340
+ ### Contextual Role Assignment and Queries
321
341
 
322
342
  ```rb
323
343
  # Assign roles within a specific model instance
@@ -335,7 +355,7 @@ user.has_role?(:admin, context: Project)
335
355
  user.revoke_roles(:owner, context: project)
336
356
 
337
357
  # Get user roles
338
- user.roles(context: Project)
358
+ user.roles(context: project)
339
359
 
340
360
  # Get users with a role
341
361
  User.with_role(:member, context: project)
@@ -359,6 +379,8 @@ Rabarber.roles(context: project)
359
379
 
360
380
  ### Contextual Authorization
361
381
 
382
+ In authorization rules, in addition to specifying context explicitly, you can also provide a proc or a symbol (similar to dynamic rules):
383
+
362
384
  ```rb
363
385
  class ProjectsController < ApplicationController
364
386
  grant_access roles: :admin, context: Project
@@ -383,11 +405,11 @@ class ProjectsController < ApplicationController
383
405
  end
384
406
  ```
385
407
 
386
- ### Orphaned Context
408
+ ### Orphaned Contextual Roles
387
409
 
388
410
  When a context object is deleted from your database, its associated roles become orphaned and ignored by Rabarber.
389
411
 
390
- To clean up orphaned context roles, use:
412
+ To clean up orphaned roles, use:
391
413
 
392
414
  ```rb
393
415
  Rabarber.prune
@@ -395,13 +417,13 @@ Rabarber.prune
395
417
 
396
418
  ### Context Migrations
397
419
 
398
- Handle context changes when models are renamed or removed. These are irreversible data migrations.
420
+ When you rename or remove models used as contexts, you need to update Rabarber's stored context data accordingly. Use these irreversible data migrations:
399
421
 
400
422
  ```rb
401
423
  # Rename a context class (e.g., when you rename your Ticket model to Task)
402
424
  migrate_authorization_context!("Ticket", "Task")
403
425
 
404
- # Remove orphaned context data (e.g., when you delete the Ticket model entirely)
426
+ # Remove context data (e.g., when you delete the Ticket model entirely)
405
427
  delete_authorization_context!("Ticket")
406
428
  ```
407
429
 
@@ -440,7 +462,7 @@ Use conditional rendering based on roles:
440
462
  ## Getting Help and Contributing
441
463
 
442
464
  ### Getting Help
443
- 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:
444
466
  - Usage questions
445
467
  - Implementation guidance
446
468
  - Feature suggestions
@@ -455,7 +477,7 @@ Found a bug? Please [create an issue](https://github.com/enjaku4/rabarber/issues
455
477
  Ready to contribute? You can:
456
478
  - Fix bugs by submitting pull requests
457
479
  - Improve documentation
458
- - 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))
459
481
 
460
482
  Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md)
461
483
 
@@ -471,5 +493,5 @@ Everyone interacting in the Rabarber project is expected to follow the [code of
471
493
 
472
494
  Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
473
495
 
474
- [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)
475
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}"
@@ -61,7 +61,7 @@ module Rabarber
61
61
  end
62
62
 
63
63
  def when_unauthorized
64
- request.format.html? ? redirect_back(fallback_location: root_path) : head(:forbidden)
64
+ request.format.html? ? redirect_back_or_to(root_path) : head(:forbidden)
65
65
  end
66
66
  end
67
67
  end
@@ -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.4"
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
@@ -6,18 +6,20 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "rabarber"
7
7
  spec.version = Rabarber::VERSION
8
8
  spec.authors = ["enjaku4", "trafium"]
9
- spec.email = ["enjaku4@icloud.com"]
9
+ spec.email = ["contact@brownbox.dev"]
10
10
  spec.homepage = "https://github.com/enjaku4/rabarber"
11
11
  spec.metadata["homepage_uri"] = spec.homepage
12
12
  spec.metadata["source_code_uri"] = spec.homepage
13
13
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
14
14
  spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
15
15
  spec.metadata["documentation_uri"] = "#{spec.homepage}/blob/main/README.md"
16
+ spec.metadata["mailing_list_uri"] = "#{spec.homepage}/discussions"
17
+ spec.metadata["funding_uri"] = "https://github.com/sponsors/enjaku4"
16
18
  spec.metadata["rubygems_mfa_required"] = "true"
17
19
  spec.summary = "Simple role-based authorization library for Ruby on Rails"
18
- spec.description = "Rabarber provides role-based authorization for Ruby on Rails applications with support for multi-tenancy, dynamic rules, and clean controller-level access control that separates authorization from business logic"
20
+ spec.description = "Simple role-based authorization for Rails applications with multi-tenancy support"
19
21
  spec.license = "MIT"
20
- spec.required_ruby_version = ">= 3.2", "< 3.5"
22
+ spec.required_ruby_version = ">= 3.3", "< 4.1"
21
23
 
22
24
  spec.files = [
23
25
  "rabarber.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
@@ -25,7 +27,5 @@ Gem::Specification.new do |spec|
25
27
 
26
28
  spec.require_paths = ["lib"]
27
29
 
28
- spec.add_dependency "dry-configurable", "~> 1.1"
29
- spec.add_dependency "dry-types", "~> 1.7"
30
- spec.add_dependency "rails", ">= 7.1", "< 8.2"
30
+ spec.add_dependency "rails", ">= 7.2", "< 8.2"
31
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.4
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,15 +26,14 @@ 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'
61
- description: Rabarber provides role-based authorization for Ruby on Rails applications
62
- with support for multi-tenancy, dynamic rules, and clean controller-level access
63
- control that separates authorization from business logic
33
+ description: Simple role-based authorization for Rails applications with multi-tenancy
34
+ support
64
35
  email:
65
- - enjaku4@icloud.com
36
+ - contact@brownbox.dev
66
37
  executables: []
67
38
  extensions: []
68
39
  extra_rdoc_files: []
@@ -89,11 +60,12 @@ files:
89
60
  - lib/rabarber/inputs/dynamic_rule.rb
90
61
  - lib/rabarber/inputs/model.rb
91
62
  - lib/rabarber/inputs/non_empty_string.rb
63
+ - lib/rabarber/inputs/non_empty_symbol.rb
92
64
  - lib/rabarber/inputs/role.rb
93
65
  - lib/rabarber/inputs/roles.rb
94
- - lib/rabarber/inputs/symbol.rb
95
66
  - lib/rabarber/models/concerns/roleable.rb
96
67
  - lib/rabarber/models/role.rb
68
+ - lib/rabarber/models/role_management.rb
97
69
  - lib/rabarber/railtie.rb
98
70
  - lib/rabarber/version.rb
99
71
  - rabarber.gemspec
@@ -106,6 +78,8 @@ metadata:
106
78
  changelog_uri: https://github.com/enjaku4/rabarber/blob/main/CHANGELOG.md
107
79
  bug_tracker_uri: https://github.com/enjaku4/rabarber/issues
108
80
  documentation_uri: https://github.com/enjaku4/rabarber/blob/main/README.md
81
+ mailing_list_uri: https://github.com/enjaku4/rabarber/discussions
82
+ funding_uri: https://github.com/sponsors/enjaku4
109
83
  rubygems_mfa_required: 'true'
110
84
  rdoc_options: []
111
85
  require_paths:
@@ -114,17 +88,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
114
88
  requirements:
115
89
  - - ">="
116
90
  - !ruby/object:Gem::Version
117
- version: '3.2'
91
+ version: '3.3'
118
92
  - - "<"
119
93
  - !ruby/object:Gem::Version
120
- version: '3.5'
94
+ version: '4.1'
121
95
  required_rubygems_version: !ruby/object:Gem::Requirement
122
96
  requirements:
123
97
  - - ">="
124
98
  - !ruby/object:Gem::Version
125
99
  version: '0'
126
100
  requirements: []
127
- rubygems_version: 3.7.2
101
+ rubygems_version: 4.0.8
128
102
  specification_version: 4
129
103
  summary: Simple role-based authorization library for Ruby on Rails
130
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