rabarber 5.1.1 → 5.2.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: 841b259e40b7e446647ee02fcca64cd183c64978c194edb6917ba344d9f12e92
4
- data.tar.gz: 044c1aa8248f7b1b16f1c78648a0b4cafb0451f31d8fed5b72f6d3343a645c48
3
+ metadata.gz: 70a773c884c0e207de25a518ff864d25ff13cd1a05c712c88efd17d264782d52
4
+ data.tar.gz: 901200cff398f2355a307aff03c9e63f1bcdbecf07369bf768fe7f12ebed9151
5
5
  SHA512:
6
- metadata.gz: a0880247a0c7f1af21dbb8099880d24ec200295e54f976855aac577fba55b692fee41a7ddfaf10f83848dfba20e2c0ac8289eb7bc94df21cabceb04da47925c1
7
- data.tar.gz: 024e3093a753d53f158532c829ca245b159448d19901d884b4c09518e70b81d39d710ee0867339ca064bc0519fd06ef53a7af507149d29e80a2a2216ad92a096
6
+ metadata.gz: dd83d11d1c831f3ecb5ea36fb6d650113453f5e30b5cfdfb1affb58a515a5f77da33eb067a03abde379997f47c7e8c44d5abdf05c3a1b363d778215241406ad0
7
+ data.tar.gz: 130c1c65c3d5bc2e33b788e35f4a790586784ced24a20a110b106303729a2c5a798330e5e05abfe6ebf58902a63761af99909443a92b2dcec496f6d26136926e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## v5.2.0
2
+
3
+ ### Features:
4
+
5
+ - Added `Rabarber.prune` method to allow manual pruning of roles with deleted contexts
6
+
7
+ ### Misc:
8
+
9
+ - `revoke_all_roles` now returns an empty array instead of `nil` for consistency with other role assignment methods
10
+ - Deprecated the following role management methods in favor of new counterparts:
11
+ - `Rabarber::Role.names` -> `Rabarber.roles`
12
+ - `Rabarber::Role.all_names` -> `Rabarber.all_roles`
13
+ - `Rabarber::Role.add` -> `Rabarber.create_role`
14
+ - `Rabarber::Role.rename` -> `Rabarber.rename_role`
15
+ - `Rabarber::Role.remove` -> `Rabarber.delete_role`
16
+ - `Rabarber::Role.assignees` -> `User.with_role`
17
+
18
+ ## v5.1.2
19
+
20
+ ### Misc:
21
+
22
+ - Minor performance improvements and code cleanup
23
+ - Relaxed dependency versions
24
+
1
25
  ## v5.1.1
2
26
 
3
27
  ### Misc:
@@ -28,7 +52,7 @@
28
52
  - Added `with_authorization` method for more granular authorization control
29
53
  - `Rabarber::Role.rename` and `Rabarber::Role.remove` now require the role to exist
30
54
 
31
- To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/brownboxdev/rabarber/discussions/77)
55
+ To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/77)
32
56
 
33
57
  ### Features:
34
58
 
@@ -133,7 +157,7 @@ To upgrade to v5.0.0, please refer to the [migration guide](https://github.com/b
133
157
 
134
158
  - Changed Rabarber roles table structure
135
159
 
136
- To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/brownboxdev/rabarber/discussions/58)
160
+ To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/58)
137
161
 
138
162
  ### Features:
139
163
 
@@ -157,7 +181,7 @@ To upgrade to v3.0.0, please refer to the [migration guide](https://github.com/b
157
181
  - Replaced `when_unauthorized` configuration option with an overridable controller method
158
182
  - Renamed `Rabarber::Role.assignees_for` method to `Rabarber::Role.assignees`
159
183
 
160
- To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/brownboxdev/rabarber/discussions/52)
184
+ To upgrade to v2.0.0, please refer to the [migration guide](https://github.com/enjaku4/rabarber/discussions/52)
161
185
 
162
186
  ### Features:
163
187
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Rabarber: Role-Based Authorization for Rails
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rabarber.svg)](http://badge.fury.io/rb/rabarber)
4
- [![Github Actions badge](https://github.com/brownboxdev/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/brownboxdev/rabarber/actions/workflows/ci.yml)
4
+ [![Github Actions badge](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
5
5
 
6
6
  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.
7
7
 
@@ -25,7 +25,6 @@ Rabarber is a role-based authorization library for Ruby on Rails that focuses on
25
25
  - [Multi-tenancy / Context](#multi-tenancy--context)
26
26
  - [View Helpers](#view-helpers)
27
27
 
28
-
29
28
  **Community Resources:**
30
29
  - [Getting Help and Contributing](#getting-help-and-contributing)
31
30
  - [License](#license)
@@ -68,13 +67,13 @@ Configure Rabarber in an initializer if customization is needed:
68
67
 
69
68
  ```rb
70
69
  Rabarber.configure do |config|
71
- config.cache_enabled = true # Enable role caching (default: true)
70
+ config.cache_enabled = true # Enable/disable role caching (default: true)
72
71
  config.current_user_method = :current_user # Method to access current user (default: :current_user)
73
72
  config.user_model_name = "User" # User model name (default: "User")
74
73
  end
75
74
  ```
76
75
 
77
- To clear the role cache manually:
76
+ Roles are cached by default for performance. To clear the role cache manually:
78
77
 
79
78
  ```rb
80
79
  Rabarber::Cache.clear
@@ -100,17 +99,22 @@ user.revoke_roles(:accountant, :manager)
100
99
  user.revoke_all_roles
101
100
  ```
102
101
 
102
+ All role assignment methods return the list of roles currently assigned to the user.
103
+
103
104
  ### Role Queries
104
105
 
105
106
  ```rb
106
107
  # Check if user has any of the specified roles
107
108
  user.has_role?(:accountant, :manager)
108
109
 
109
- # Get user's roles
110
+ # Get user's roles in the global context
110
111
  user.roles
111
112
 
112
- # Get all roles grouped by context
113
+ # Get all user's roles grouped by context
113
114
  user.all_roles
115
+
116
+ # Get users with any of the specified roles
117
+ User.with_role(:admin, :manager)
114
118
  ```
115
119
 
116
120
  ## Role Management
@@ -119,24 +123,25 @@ user.all_roles
119
123
 
120
124
  ```rb
121
125
  # Create a new role
122
- Rabarber::Role.add(:admin)
126
+ Rabarber.create_role(:admin) # => true if created, false if already exists
123
127
 
124
128
  # Rename a role
125
- Rabarber::Role.rename(:admin, :administrator)
126
- Rabarber::Role.rename(:admin, :administrator, force: true) # Force if role is assigned to users
129
+ Rabarber.rename_role(:admin, :administrator) # => true if renamed, false if new name exists or role is assigned
130
+ Rabarber.rename_role(:admin, :administrator, force: true) # Force rename even if role is assigned
127
131
 
128
132
  # Remove a role
129
- Rabarber::Role.remove(:admin)
130
- Rabarber::Role.remove(:admin, force: true) # Force if role is assigned to users
133
+ Rabarber.delete_role(:admin) # => true if deleted, false if role is assigned
134
+ Rabarber.delete_role(:admin, force: true) # Force deletion even if role is assigned
131
135
 
132
- # List available roles
133
- Rabarber::Role.names
134
- Rabarber::Role.all_names # All roles grouped by context
136
+ # List available roles in the global context
137
+ Rabarber.roles
135
138
 
136
- # Get users assigned to a role
137
- Rabarber::Role.assignees(:admin)
139
+ # List all available roles grouped by context
140
+ Rabarber.all_roles
138
141
  ```
139
142
 
143
+ > **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.
144
+
140
145
  ## Controller Authorization
141
146
 
142
147
  ### Basic Setup
@@ -155,7 +160,7 @@ class InvoicesController < ApplicationController
155
160
  end
156
161
  ```
157
162
 
158
- Authorization requires an authenticated user.
163
+ Authorization requires an authenticated user to be present.
159
164
 
160
165
  ### Skip Authorization
161
166
 
@@ -182,9 +187,8 @@ class TicketsController < ApplicationController
182
187
  # Accessible to admin, manager, and support roles
183
188
  end
184
189
 
185
- grant_access action: :destroy, roles: :owner, context: -> { Ticket.find(params[:id]) }
186
190
  def destroy
187
- # Accessible to admin and owner of the ticket
191
+ # Accessible to admin role only
188
192
  end
189
193
  end
190
194
  ```
@@ -215,7 +219,7 @@ Omit roles to allow unrestricted access:
215
219
 
216
220
  ```rb
217
221
  class UnrestrictedController < ApplicationController
218
- grant_access # Allow all users
222
+ grant_access # Allow all users to access all actions
219
223
  end
220
224
 
221
225
  class MixedController < ApplicationController
@@ -244,14 +248,12 @@ class ApplicationController < ActionController::Base
244
248
  private
245
249
 
246
250
  def when_unauthorized
247
- # Default behavior: redirect back (HTML) or return 403 (other formats)
248
- # Custom behavior example:
249
- head :not_found # Hide existence of protected resources
251
+ head :not_found # Custom behavior to hide existence of protected resources
250
252
  end
251
253
  end
252
254
  ```
253
255
 
254
- By default, Rabarber will redirect back (HTML format) or return 403 (other formats).
256
+ By default, when unauthorized, Rabarber will redirect back (HTML format) or return 403 (other formats).
255
257
 
256
258
  ## Dynamic Rules
257
259
 
@@ -263,13 +265,13 @@ class OrdersController < ApplicationController
263
265
  grant_access roles: :manager, if: :company_manager?, unless: :suspended?
264
266
 
265
267
  # Proc-based conditions
266
- grant_access action: :show, roles: :client, if: -> { current_user.company_id == Order.find(params[:id]).company_id }
268
+ grant_access action: :show, roles: :client, if: -> { current_user.company == Order.find(params[:id]).company }
267
269
  def show
268
270
  # Accessible to company managers unless suspended, and to clients if the client's company matches the order's company
269
271
  end
270
272
 
271
273
  # Dynamic-only rules (no roles required, can be used with custom policies)
272
- grant_access action: :index, if: -> { OrdersPolicy.new(current_user).can_access?(:index) }
274
+ grant_access action: :index, if: -> { OrdersPolicy.new(current_user).index? }
273
275
  def index
274
276
  # Accessible to company managers unless suspended, and to users based on custom policy logic
275
277
  end
@@ -288,7 +290,7 @@ end
288
290
 
289
291
  ## Multi-tenancy / Context
290
292
 
291
- All Rabarber methods accept a `context` parameter, allowing you to work with roles within specific scopes rather than globally.
293
+ 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.
292
294
 
293
295
  ### Contextual Role Assignment
294
296
 
@@ -297,32 +299,52 @@ All Rabarber methods accept a `context` parameter, allowing you to work with rol
297
299
  user.assign_roles(:owner, context: project)
298
300
  user.assign_roles(:member, context: project)
299
301
 
300
- # Assign roles within a model class (e.g., project admin)
302
+ # Assign roles within a model class
301
303
  user.assign_roles(:admin, context: Project)
302
304
 
303
305
  # Check contextual roles
304
306
  user.has_role?(:owner, context: project)
305
307
  user.has_role?(:admin, context: Project)
306
308
 
307
- # Get roles within context
308
- user.roles(context: project)
309
- Rabarber::Role.names(context: Project)
309
+ # Revoke roles
310
+ user.revoke_roles(:owner, context: project)
311
+
312
+ # Get user roles
313
+ user.roles(context: Project)
314
+
315
+ # Get users with a role
316
+ User.with_role(:member, context: project)
317
+ ```
318
+
319
+ ### Contextual Role Management
320
+
321
+ ```rb
322
+ # Create a new role within a context
323
+ Rabarber.create_role(:admin, context: Project)
324
+
325
+ # Rename a role within a context
326
+ Rabarber.rename_role(:admin, :owner, context: project)
327
+
328
+ # Remove a contextual role
329
+ Rabarber.delete_role(:admin, context: project)
330
+
331
+ # List available roles within a specific context
332
+ Rabarber.roles(context: project)
310
333
  ```
311
334
 
312
335
  ### Contextual Authorization
313
336
 
314
337
  ```rb
315
338
  class ProjectsController < ApplicationController
316
- # Class-based context
317
339
  grant_access roles: :admin, context: Project
318
340
 
319
- # Instance-based context (method)
341
+ # Method-based context resolution
320
342
  grant_access action: :show, roles: :member, context: :current_project
321
343
  def show
322
344
  # Accessible to Project admin and members of the current project
323
345
  end
324
346
 
325
- # Instance-based context (proc)
347
+ # Proc-based context resolution
326
348
  grant_access action: :update, roles: :owner, context: -> { Project.find(params[:id]) }
327
349
  def update
328
350
  # Accessible to Project admin and owner of the current project
@@ -336,16 +358,26 @@ class ProjectsController < ApplicationController
336
358
  end
337
359
  ```
338
360
 
361
+ ### Orphaned Context
362
+
363
+ When a context object is deleted from your database, its associated roles become orphaned and ignored by Rabarber.
364
+
365
+ To clean up orphaned context roles, use:
366
+
367
+ ```rb
368
+ Rabarber.prune
369
+ ```
370
+
339
371
  ### Context Migrations
340
372
 
341
373
  Handle context changes when models are renamed or removed. These are irreversible data migrations.
342
374
 
343
375
  ```rb
344
- # Rename a context class (e.g., when you rename your Project model to Campaign)
345
- migrate_authorization_context!("Project", "Campaign")
376
+ # Rename a context class (e.g., when you rename your Ticket model to Task)
377
+ migrate_authorization_context!("Ticket", "Task")
346
378
 
347
- # Remove orphaned context data (e.g., when you delete a model entirely)
348
- delete_authorization_context!("DeletedModel")
379
+ # Remove orphaned context data (e.g., when you delete the Ticket model entirely)
380
+ delete_authorization_context!("Ticket")
349
381
  ```
350
382
 
351
383
  ## View Helpers
@@ -373,22 +405,23 @@ Use conditional rendering based on roles:
373
405
  </div>
374
406
  <% end %>
375
407
 
376
- <!-- With context -->
377
408
  <%= visible_to(:owner, context: @project) do %>
378
- <button>Delete Project</button>
409
+ <div class="project-owner-panel">
410
+ <!-- Content visible to project owners -->
411
+ </div>
379
412
  <% end %>
380
413
  ```
381
414
 
382
415
  ## Getting Help and Contributing
383
416
 
384
417
  ### Getting Help
385
- Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/brownboxdev/rabarber/discussions) for:
418
+ Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/enjaku4/rabarber/discussions) for:
386
419
  - Usage questions
387
420
  - Implementation guidance
388
421
  - Feature suggestions
389
422
 
390
423
  ### Reporting Issues
391
- Found a bug? Please [create an issue](https://github.com/brownboxdev/rabarber/issues) with:
424
+ Found a bug? Please [create an issue](https://github.com/enjaku4/rabarber/issues) with:
392
425
  - A clear description of the problem
393
426
  - Steps to reproduce the issue
394
427
  - Your environment details (Rails version, Ruby version, etc.)
@@ -397,21 +430,21 @@ Found a bug? Please [create an issue](https://github.com/brownboxdev/rabarber/is
397
430
  Ready to contribute? You can:
398
431
  - Fix bugs by submitting pull requests
399
432
  - Improve documentation
400
- - Add new features (please discuss first in our [discussions section](https://github.com/brownboxdev/rabarber/discussions))
433
+ - Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/rabarber/discussions))
401
434
 
402
- Before contributing, please read the [contributing guidelines](https://github.com/brownboxdev/rabarber/blob/main/CONTRIBUTING.md)
435
+ Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md)
403
436
 
404
437
  ## License
405
438
 
406
- The gem is available as open source under the terms of the [MIT License](https://github.com/brownboxdev/rabarber/blob/main/LICENSE.txt).
439
+ The gem is available as open source under the terms of the [MIT License](https://github.com/enjaku4/rabarber/blob/main/LICENSE.txt).
407
440
 
408
441
  ## Code of Conduct
409
442
 
410
- Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/brownboxdev/rabarber/blob/main/CODE_OF_CONDUCT.md).
443
+ Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/enjaku4/rabarber/blob/main/CODE_OF_CONDUCT.md).
411
444
 
412
445
  ## Old Versions
413
446
 
414
447
  Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
415
448
 
416
- [v4.x.x](https://github.com/brownboxdev/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/brownboxdev/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
417
- | [v2.x.x](https://github.com/brownboxdev/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/brownboxdev/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
449
+ [v4.x.x](https://github.com/enjaku4/rabarber/blob/9353e70281971154d5acd70693620197a132c543/README.md) | [v3.x.x](https://github.com/enjaku4/rabarber/blob/3bb273de7e342004abc7ef07fa4d0a9a3ce3e249/README.md)
450
+ | [v2.x.x](https://github.com/enjaku4/rabarber/blob/875b357ea949404ddc3645ad66eddea7ed4e2ee4/README.md) | [v1.x.x](https://github.com/enjaku4/rabarber/blob/b81428429404e197d70317b763e7b2a21e02c296/README.md)
@@ -12,43 +12,39 @@ module Rabarber
12
12
  default: true,
13
13
  reader: true,
14
14
  constructor: -> (value) do
15
- Rabarber::Inputs.process(
15
+ Rabarber::Inputs::Boolean.new(
16
16
  value,
17
- as: :boolean,
18
17
  error: Rabarber::ConfigurationError,
19
18
  message: "Invalid configuration `cache_enabled`, expected a boolean, got #{value.inspect}"
20
- )
19
+ ).process
21
20
  end
22
21
  setting :current_user_method,
23
22
  default: :current_user,
24
23
  reader: true,
25
24
  constructor: -> (value) do
26
- Rabarber::Inputs.process(
25
+ Rabarber::Inputs::Symbol.new(
27
26
  value,
28
- as: :symbol,
29
27
  error: Rabarber::ConfigurationError,
30
28
  message: "Invalid configuration `current_user_method`, expected a symbol or a string, got #{value.inspect}"
31
- )
29
+ ).process
32
30
  end
33
31
  setting :user_model_name,
34
32
  default: "User",
35
33
  reader: true,
36
34
  constructor: -> (value) do
37
- Rabarber::Inputs.process(
35
+ Rabarber::Inputs::NonEmptyString.new(
38
36
  value,
39
- as: :string,
40
37
  error: Rabarber::ConfigurationError,
41
38
  message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{value.inspect}"
42
- )
39
+ ).process
43
40
  end
44
41
 
45
42
  def user_model
46
- Rabarber::Inputs.process(
43
+ Rabarber::Inputs::Model.new(
47
44
  user_model_name,
48
- as: :model,
49
45
  error: Rabarber::ConfigurationError,
50
46
  message: "Invalid configuration `user_model_name`, expected an ActiveRecord model name, got #{user_model_name.inspect}"
51
- )
47
+ ).process
52
48
  end
53
49
  end
54
50
  end
@@ -19,36 +19,35 @@ module Rabarber
19
19
  end
20
20
 
21
21
  def grant_access(action: nil, roles: nil, context: nil, if: nil, unless: nil)
22
+ if_rule = binding.local_variable_get(:if)
23
+ unless_rule = binding.local_variable_get(:unless)
24
+
22
25
  Rabarber::Core::Permissions.add(
23
26
  self,
24
- Rabarber::Inputs.process(
27
+ Rabarber::Inputs::Symbol.new(
25
28
  action,
26
- as: :symbol,
27
29
  optional: true,
28
30
  message: "Expected a symbol or a string, got #{action.inspect}"
29
- ),
30
- Rabarber::Inputs.process(
31
+ ).process,
32
+ Rabarber::Inputs::Roles.new(
31
33
  roles,
32
- as: :roles,
33
34
  message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{roles.inspect}"
34
- ),
35
- Rabarber::Inputs.process(
35
+ ).process,
36
+ Rabarber::Inputs::Contexts::Authorizational.new(
36
37
  context,
37
- as: :authorization_context,
38
+ error: Rabarber::InvalidContextError,
38
39
  message: "Expected a Class, an instance of ActiveRecord model, a symbol, a string, or a proc, got #{context.inspect}"
39
- ),
40
- Rabarber::Inputs.process(
41
- binding.local_variable_get(:if),
42
- as: :dynamic_rule,
40
+ ).resolve,
41
+ Rabarber::Inputs::DynamicRule.new(
42
+ if_rule,
43
43
  optional: true,
44
- message: "Expected a symbol, a string, or a proc, got #{binding.local_variable_get(:if).inspect}"
45
- ),
46
- Rabarber::Inputs.process(
47
- binding.local_variable_get(:unless),
48
- as: :dynamic_rule,
44
+ message: "Expected a symbol, a string, or a proc, got #{if_rule.inspect}"
45
+ ).process,
46
+ Rabarber::Inputs::DynamicRule.new(
47
+ unless_rule,
49
48
  optional: true,
50
- message: "Expected a symbol, a string, or a proc, got #{binding.local_variable_get(:unless).inspect}"
51
- )
49
+ message: "Expected a symbol, a string, or a proc, got #{unless_rule.inspect}"
50
+ ).process
52
51
  )
53
52
  end
54
53
  end
@@ -22,7 +22,7 @@ module Rabarber
22
22
  class << self
23
23
  def add(controller, action, roles, context, dynamic_rule, negated_dynamic_rule)
24
24
  rule = Rabarber::Core::Rule.new(roles, context, dynamic_rule, negated_dynamic_rule)
25
- action ? action_rules[controller][action] += [rule] : controller_rules[controller] += [rule]
25
+ action ? action_rules[controller][action] << rule : controller_rules[controller] << rule
26
26
  end
27
27
 
28
28
  def controller_rules
@@ -3,8 +3,6 @@
3
3
  module Rabarber
4
4
  module Core
5
5
  class Rule
6
- attr_reader :roles, :context, :dynamic_rule, :negated_dynamic_rule
7
-
8
6
  DEFAULT_DYNAMIC_RULE = -> { true }.freeze
9
7
  DEFAULT_NEGATED_DYNAMIC_RULE = -> { false }.freeze
10
8
  private_constant :DEFAULT_DYNAMIC_RULE, :DEFAULT_NEGATED_DYNAMIC_RULE
@@ -21,11 +19,11 @@ module Rabarber
21
19
  end
22
20
 
23
21
  def roles_permitted?(roleable, controller_instance)
24
- roles.empty? || roleable.has_role?(*roles, context: resolve_context(controller_instance))
22
+ @roles.empty? || roleable.has_role?(*@roles, context: resolve_context(controller_instance))
25
23
  end
26
24
 
27
25
  def dynamic_rules_followed?(controller_instance)
28
- execute_rule(controller_instance, dynamic_rule) && !execute_rule(controller_instance, negated_dynamic_rule)
26
+ execute_rule(controller_instance, @dynamic_rule) && !execute_rule(controller_instance, @negated_dynamic_rule)
29
27
  end
30
28
 
31
29
  private
@@ -35,12 +33,17 @@ module Rabarber
35
33
  end
36
34
 
37
35
  def resolve_context(controller_instance)
38
- resolved_context = case context
39
- when Proc then controller_instance.instance_exec(&context)
40
- when Symbol then controller_instance.send(context)
41
- else context
42
- end
43
- Rabarber::Inputs.process(resolved_context, as: :role_context, message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{resolved_context.inspect}")
36
+ context = case @context
37
+ when Proc then controller_instance.instance_exec(&@context)
38
+ when Symbol then controller_instance.send(@context)
39
+ else @context
40
+ end
41
+
42
+ Rabarber::Inputs::Context.new(
43
+ context,
44
+ error: Rabarber::InvalidContextError,
45
+ message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
46
+ ).resolve
44
47
  end
45
48
  end
46
49
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module Rabarber
6
+ module Inputs
7
+ class Base
8
+ include Dry.Types()
9
+
10
+ def initialize(value, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
11
+ @value = value
12
+ @optional = optional
13
+ @error = error
14
+ @message = message
15
+ end
16
+
17
+ def process
18
+ type_checker = @optional ? type.optional : type
19
+ type_checker[@value]
20
+ rescue Dry::Types::CoercionError
21
+ raise_error
22
+ end
23
+
24
+ private
25
+
26
+ def type
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def raise_error
31
+ raise @error, @message
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class Boolean < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def type = self.class::Strict::Bool
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class Context < Rabarber::Inputs::Base
6
+ def resolve
7
+ case context = process
8
+ when nil
9
+ { context_type: nil, context_id: nil }
10
+ when Class
11
+ { context_type: context.to_s, context_id: nil }
12
+ when ActiveRecord::Base
13
+ raise_error unless context.persisted?
14
+ { context_type: context.class.to_s, context_id: context.public_send(context.class.primary_key) }
15
+ else
16
+ context
17
+ end
18
+ end
19
+
20
+ private
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
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ module Contexts
6
+ class Authorizational < Rabarber::Inputs::Context
7
+ private
8
+
9
+ def type = self.class::Coercible::Symbol.constrained(min_size: 1) | self.class::Instance(Proc) | super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class DynamicRule < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def type = self.class::Coercible::Symbol.constrained(min_size: 1) | self.class::Instance(Proc)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class Model < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def type = self.class::Strict::Class.constructor { _1.try(:safe_constantize) }.constrained(lt: ActiveRecord::Base)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class NonEmptyString < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def type = self.class::Strict::String.constrained(min_size: 1)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class Role < Rabarber::Inputs::Base
6
+ private
7
+
8
+ def type = self.class::Coercible::Symbol.constrained(min_size: 1, format: /\A[a-z0-9_]+\z/)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Inputs
5
+ class Roles < Rabarber::Inputs::Base
6
+ private
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) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
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
@@ -1,17 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- module HasRoles
4
+ module Roleable
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- raise Rabarber::Error, "Rabarber::HasRoles can only be included once" if defined?(@@included) && @@included != name
8
+ raise Rabarber::Error, "Rabarber::Roleable can only be included once" if defined?(@@included) && @@included != name
9
9
 
10
10
  @@included = name
11
11
 
12
12
  has_and_belongs_to_many :rabarber_roles, class_name: "Rabarber::Role",
13
13
  foreign_key: "roleable_id",
14
14
  join_table: "rabarber_roles_roleables"
15
+
16
+ scope :with_role, -> (*role_names, context: nil) {
17
+ joins(:rabarber_roles).where(
18
+ rabarber_roles: { name: process_role_names(role_names), **process_context(context) }
19
+ ).distinct
20
+ }
21
+
22
+ class << self
23
+ def process_role_names(role_names)
24
+ Rabarber::Inputs::Roles.new(
25
+ role_names,
26
+ message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{role_names.inspect}"
27
+ ).process
28
+ end
29
+
30
+ def process_context(context)
31
+ Rabarber::Inputs::Context.new(
32
+ context,
33
+ error: Rabarber::InvalidContextError,
34
+ message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
35
+ ).resolve
36
+ end
37
+ end
38
+ delegate :process_role_names, :process_context, to: :class, private: true
15
39
  end
16
40
 
17
41
  def roles(context: nil)
@@ -64,13 +88,15 @@ module Rabarber
64
88
  end
65
89
 
66
90
  def revoke_all_roles
67
- return if rabarber_roles.none?
91
+ return [] if rabarber_roles.none?
68
92
 
69
93
  contexts = all_roles.keys.map { process_context(_1) }
70
94
 
71
95
  rabarber_roles.clear
72
96
 
73
97
  delete_roleable_cache(contexts:)
98
+
99
+ []
74
100
  end
75
101
 
76
102
  private
@@ -80,22 +106,6 @@ module Rabarber
80
106
  new_roles.each { |role_name| Rabarber::Role.create!(name: role_name, **context) }
81
107
  end
82
108
 
83
- def process_role_names(role_names)
84
- Rabarber::Inputs.process(
85
- role_names,
86
- as: :roles,
87
- message: "Expected an array of symbols or strings containing only lowercase letters, numbers, and underscores, got #{role_names.inspect}"
88
- )
89
- end
90
-
91
- def process_context(context)
92
- Rabarber::Inputs.process(
93
- context,
94
- as: :role_context,
95
- message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
96
- )
97
- end
98
-
99
109
  def delete_roleable_cache(contexts:)
100
110
  Rabarber::Core::Cache.delete(*contexts.map { [roleable_id, _1] }, [roleable_id, :all])
101
111
  end
@@ -12,10 +12,14 @@ module Rabarber
12
12
 
13
13
  class << self
14
14
  def names(context: nil)
15
+ deprecation_warning("names", "Rabarber.roles")
16
+
15
17
  where(process_context(context)).pluck(:name).map(&:to_sym)
16
18
  end
17
19
 
18
20
  def all_names
21
+ deprecation_warning("all_names", "Rabarber.all_roles")
22
+
19
23
  includes(:context).each_with_object({}) do |role, hash|
20
24
  (hash[role.context] ||= []) << role.name.to_sym
21
25
  rescue ActiveRecord::RecordNotFound
@@ -26,6 +30,8 @@ module Rabarber
26
30
  end
27
31
 
28
32
  def add(name, context: nil)
33
+ deprecation_warning("add", "Rabarber.create_role")
34
+
29
35
  name = process_role_name(name)
30
36
  processed_context = process_context(context)
31
37
 
@@ -35,6 +41,8 @@ module Rabarber
35
41
  end
36
42
 
37
43
  def rename(old_name, new_name, context: nil, force: false)
44
+ deprecation_warning("rename", "Rabarber.rename_role")
45
+
38
46
  processed_context = process_context(context)
39
47
  role = find_by(name: process_role_name(old_name), **processed_context)
40
48
 
@@ -50,6 +58,8 @@ module Rabarber
50
58
  end
51
59
 
52
60
  def remove(name, context: nil, force: false)
61
+ deprecation_warning("remove", "Rabarber.delete_role")
62
+
53
63
  processed_context = process_context(context)
54
64
  role = find_by(name: process_role_name(name), **processed_context)
55
65
 
@@ -63,31 +73,53 @@ module Rabarber
63
73
  end
64
74
 
65
75
  def assignees(name, context: nil)
76
+ deprecation_warning("assignees", "#{Rabarber::Configuration.user_model_name}.with_role")
77
+
66
78
  find_by(name: process_role_name(name), **process_context(context))&.roleables || Rabarber::Configuration.user_model.none
67
79
  end
68
80
 
81
+ def prune
82
+ orphaned_roles = where.not(context_id: nil).includes(:context).filter_map do |role|
83
+ !role.context
84
+ rescue ActiveRecord::RecordNotFound
85
+ role.id
86
+ end
87
+
88
+ return if orphaned_roles.empty?
89
+
90
+ ActiveRecord::Base.transaction do
91
+ ActiveRecord::Base.connection.execute(
92
+ ActiveRecord::Base.sanitize_sql(
93
+ ["DELETE FROM rabarber_roles_roleables WHERE role_id IN (?)", orphaned_roles]
94
+ )
95
+ )
96
+ where(id: orphaned_roles).delete_all
97
+ end
98
+ end
99
+
69
100
  private
70
101
 
102
+ def deprecation_warning(method, alternative)
103
+ 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.") if caller_locations.map(&:label).exclude?(alternative)
104
+ end
105
+
71
106
  def delete_roleables_cache(role, context:)
72
- role.roleables.in_batches(of: 1000) do |batch|
73
- Rabarber::Core::Cache.delete(*batch.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
74
- end
107
+ Rabarber::Core::Cache.delete(*role.roleables.pluck(:id).flat_map { [[_1, context], [_1, :all]] })
75
108
  end
76
109
 
77
110
  def process_role_name(name)
78
- Rabarber::Inputs.process(
111
+ Rabarber::Inputs::Role.new(
79
112
  name,
80
- as: :role,
81
113
  message: "Expected a symbol or a string containing only lowercase letters, numbers, and underscores, got #{name.inspect}"
82
- )
114
+ ).process
83
115
  end
84
116
 
85
117
  def process_context(context)
86
- Rabarber::Inputs.process(
118
+ Rabarber::Inputs::Context.new(
87
119
  context,
88
- as: :role_context,
120
+ error: Rabarber::InvalidContextError,
89
121
  message: "Expected an instance of ActiveRecord model, a Class, or nil, got #{context.inspect}"
90
- )
122
+ ).resolve
91
123
  end
92
124
  end
93
125
 
@@ -6,26 +6,18 @@ module Rabarber
6
6
  class Railtie < Rails::Railtie
7
7
  initializer "rabarber.to_prepare" do |app|
8
8
  app.config.to_prepare do
9
- unless app.config.eager_load
10
- Rabarber::Core::Permissions.reset!
11
- ApplicationController.class_eval do
12
- before_action :check_integrity
13
-
14
- private
15
-
16
- def check_integrity
17
- Rabarber::Core::IntegrityChecker.run!
18
- end
9
+ if ActiveRecord::Base.connection.data_source_exists?("rabarber_roles")
10
+ Rabarber::Role.where.not(context_type: nil).distinct.pluck(:context_type).each do |context_class|
11
+ context_class.constantize
12
+ rescue NameError => e
13
+ raise Rabarber::Error, "Context not found: class `#{e.name}` may have been renamed or deleted"
19
14
  end
20
15
  end
21
- user_model = Rabarber::Configuration.user_model
22
- user_model.include Rabarber::HasRoles unless user_model < Rabarber::HasRoles
23
- end
24
- end
25
16
 
26
- initializer "rabarber.after_initialize" do |app|
27
- app.config.after_initialize do
28
- Rabarber::Core::IntegrityChecker.run! if app.config.eager_load
17
+ Rabarber::Core::Permissions.reset! unless app.config.eager_load
18
+
19
+ user_model = Rabarber::Configuration.user_model
20
+ user_model.include Rabarber::Roleable unless user_model < Rabarber::Roleable
29
21
  end
30
22
  end
31
23
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "5.1.1"
4
+ VERSION = "5.2.0"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -5,18 +5,37 @@ require_relative "rabarber/version"
5
5
  require "active_record"
6
6
  require "active_support"
7
7
 
8
- require_relative "rabarber/inputs"
8
+ require_relative "rabarber/inputs/base"
9
+ require_relative "rabarber/inputs/boolean"
10
+ require_relative "rabarber/inputs/context"
11
+ require_relative "rabarber/inputs/contexts/authorizational"
12
+ require_relative "rabarber/inputs/dynamic_rule"
13
+ require_relative "rabarber/inputs/model"
14
+ require_relative "rabarber/inputs/non_empty_string"
15
+ require_relative "rabarber/inputs/role"
16
+ require_relative "rabarber/inputs/roles"
17
+ require_relative "rabarber/inputs/symbol"
9
18
 
10
19
  require_relative "rabarber/configuration"
11
20
 
12
21
  module Rabarber
13
22
  class Error < StandardError; end
14
- class ConfigurationError < Rabarber::Error; end
15
23
  class InvalidArgumentError < Rabarber::Error; end
24
+ class ConfigurationError < Rabarber::InvalidArgumentError; end
25
+ class InvalidContextError < Rabarber::InvalidArgumentError; end
16
26
  class NotFoundError < Rabarber::Error; end
17
27
 
18
28
  delegate :configure, to: Rabarber::Configuration
19
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
20
39
  end
21
40
 
22
41
  require_relative "rabarber/core/cache"
@@ -26,10 +45,9 @@ require_relative "rabarber/core/roleable"
26
45
  require_relative "rabarber/controllers/concerns/authorization"
27
46
  require_relative "rabarber/helpers/helpers"
28
47
  require_relative "rabarber/helpers/migration_helpers"
29
- require_relative "rabarber/models/concerns/has_roles"
48
+ require_relative "rabarber/models/concerns/roleable"
30
49
  require_relative "rabarber/models/role"
31
50
 
32
51
  require_relative "rabarber/core/permissions"
33
- require_relative "rabarber/core/integrity_checker"
34
52
 
35
53
  require_relative "rabarber/railtie"
data/rabarber.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "rabarber"
7
7
  spec.version = Rabarber::VERSION
8
8
  spec.authors = ["enjaku4", "trafium"]
9
- spec.homepage = "https://github.com/brownboxdev/rabarber"
9
+ spec.homepage = "https://github.com/enjaku4/rabarber"
10
10
  spec.metadata["homepage_uri"] = spec.homepage
11
11
  spec.metadata["source_code_uri"] = spec.homepage
12
12
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency "dry-configurable", "~> 1.3"
25
- spec.add_dependency "dry-types", "~> 1.8"
24
+ spec.add_dependency "dry-configurable", "~> 1.1"
25
+ spec.add_dependency "dry-types", "~> 1.7"
26
26
  spec.add_dependency "rails", ">= 7.1", "< 8.1"
27
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabarber
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '1.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.3'
26
+ version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dry-types
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.8'
33
+ version: '1.7'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.8'
40
+ version: '1.7'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,25 +72,33 @@ files:
72
72
  - lib/rabarber/controllers/concerns/authorization.rb
73
73
  - lib/rabarber/core/access.rb
74
74
  - lib/rabarber/core/cache.rb
75
- - lib/rabarber/core/integrity_checker.rb
76
75
  - lib/rabarber/core/permissions.rb
77
76
  - lib/rabarber/core/roleable.rb
78
77
  - lib/rabarber/core/rule.rb
79
78
  - lib/rabarber/helpers/helpers.rb
80
79
  - lib/rabarber/helpers/migration_helpers.rb
81
- - lib/rabarber/inputs.rb
82
- - lib/rabarber/models/concerns/has_roles.rb
80
+ - lib/rabarber/inputs/base.rb
81
+ - lib/rabarber/inputs/boolean.rb
82
+ - lib/rabarber/inputs/context.rb
83
+ - lib/rabarber/inputs/contexts/authorizational.rb
84
+ - lib/rabarber/inputs/dynamic_rule.rb
85
+ - lib/rabarber/inputs/model.rb
86
+ - lib/rabarber/inputs/non_empty_string.rb
87
+ - lib/rabarber/inputs/role.rb
88
+ - lib/rabarber/inputs/roles.rb
89
+ - lib/rabarber/inputs/symbol.rb
90
+ - lib/rabarber/models/concerns/roleable.rb
83
91
  - lib/rabarber/models/role.rb
84
92
  - lib/rabarber/railtie.rb
85
93
  - lib/rabarber/version.rb
86
94
  - rabarber.gemspec
87
- homepage: https://github.com/brownboxdev/rabarber
95
+ homepage: https://github.com/enjaku4/rabarber
88
96
  licenses:
89
97
  - MIT
90
98
  metadata:
91
- homepage_uri: https://github.com/brownboxdev/rabarber
92
- source_code_uri: https://github.com/brownboxdev/rabarber
93
- changelog_uri: https://github.com/brownboxdev/rabarber/blob/main/CHANGELOG.md
99
+ homepage_uri: https://github.com/enjaku4/rabarber
100
+ source_code_uri: https://github.com/enjaku4/rabarber
101
+ changelog_uri: https://github.com/enjaku4/rabarber/blob/main/CHANGELOG.md
94
102
  rubygems_mfa_required: 'true'
95
103
  rdoc_options: []
96
104
  require_paths:
@@ -109,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
117
  - !ruby/object:Gem::Version
110
118
  version: '0'
111
119
  requirements: []
112
- rubygems_version: 3.6.7
120
+ rubygems_version: 3.7.1
113
121
  specification_version: 4
114
122
  summary: Simple role-based authorization library for Ruby on Rails
115
123
  test_files: []
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rabarber
4
- module Core
5
- module IntegrityChecker
6
- extend self
7
-
8
- def run!
9
- check_for_missing_class_context
10
- prune_missing_instance_context
11
- end
12
-
13
- private
14
-
15
- def check_for_missing_class_context
16
- Rabarber::Role.where.not(context_type: nil).distinct.pluck(:context_type).each do |context_class|
17
- context_class.constantize
18
- rescue NameError => e
19
- raise Rabarber::Error, "Context not found: class #{e.name} may have been renamed or deleted"
20
- end
21
- end
22
-
23
- def prune_missing_instance_context
24
- ids = Rabarber::Role.where.not(context_id: nil).includes(:context).filter_map do |role|
25
- role.context
26
- nil
27
- rescue ActiveRecord::RecordNotFound
28
- role.id
29
- end
30
-
31
- return if ids.empty?
32
-
33
- ActiveRecord::Base.transaction do
34
- ActiveRecord::Base.connection.execute(
35
- ActiveRecord::Base.sanitize_sql(
36
- ["DELETE FROM rabarber_roles_roleables WHERE role_id IN (?)", ids]
37
- )
38
- )
39
- Rabarber::Role.where(id: ids).delete_all
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry-types"
4
-
5
- module Rabarber
6
- module Inputs
7
- extend self
8
-
9
- include Dry.Types()
10
-
11
- PROC_TYPE = self::Instance(Proc)
12
- SYMBOL_TYPE = self::Coercible::Symbol.constrained(min_size: 1)
13
- ROLE_TYPE = self::SYMBOL_TYPE.constrained(format: /\A[a-z0-9_]+\z/)
14
-
15
- CONTEXT_TYPE = self::Strict::Class | self::Instance(ActiveRecord::Base) | self::Hash.schema(
16
- context_type: self::Strict::String | self::Nil,
17
- context_id: self::Strict::String | self::Strict::Integer | self::Nil
18
- ) | self::Nil
19
-
20
- TYPES = {
21
- boolean: self::Strict::Bool,
22
- string: self::Strict::String.constrained(min_size: 1).constructor { _1.is_a?(::String) ? _1.strip : _1 },
23
- symbol: self::SYMBOL_TYPE,
24
- role: self::ROLE_TYPE,
25
- roles: self::Array.of(self::ROLE_TYPE).constructor { Kernel::Array(_1) },
26
- model: self::Strict::Class.constructor { _1.try(:safe_constantize) }.constrained(lt: ActiveRecord::Base),
27
- dynamic_rule: self::SYMBOL_TYPE | self::PROC_TYPE,
28
- role_context: self::CONTEXT_TYPE,
29
- authorization_context: self::SYMBOL_TYPE | self::PROC_TYPE | self::CONTEXT_TYPE
30
- }.freeze
31
-
32
- def process(value, as:, optional: false, error: Rabarber::InvalidArgumentError, message: nil)
33
- checker = type_for(as)
34
- checker = checker.optional if optional
35
-
36
- result = checker[value]
37
-
38
- [:role_context, :authorization_context].include?(as) ? resolve_context(result) : result
39
- rescue Dry::Types::CoercionError => e
40
- raise error, message || e.message
41
- end
42
-
43
- private
44
-
45
- def type_for(name) = self::TYPES.fetch(name)
46
-
47
- def resolve_context(value)
48
- case value
49
- when nil
50
- { context_type: nil, context_id: nil }
51
- when Class
52
- { context_type: value.to_s, context_id: nil }
53
- when ActiveRecord::Base
54
- raise Dry::Types::CoercionError, "instance context not persisted" unless value.persisted?
55
-
56
- { context_type: value.class.to_s, context_id: value.public_send(value.class.primary_key) }
57
- else
58
- value
59
- end
60
- end
61
- end
62
- end