consent 1.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -6
  3. data/.rubocop.yml +17 -0
  4. data/.rubocop_todo.yml +9 -11
  5. data/Appraisals +13 -0
  6. data/Gemfile +10 -1
  7. data/Gemfile.lock +259 -0
  8. data/Rakefile +9 -3
  9. data/app/models/concerns/consent/authorizable.rb +94 -0
  10. data/app/models/consent/application_record.rb +7 -0
  11. data/app/models/consent/history.rb +21 -0
  12. data/app/models/consent/permission.rb +71 -0
  13. data/bin/console +3 -3
  14. data/config.ru +9 -0
  15. data/consent.gemspec +25 -21
  16. data/db/migrate/20211104225614_create_nitro_auth_authorization_permissions.rb +19 -0
  17. data/db/migrate/20220420135558_create_nitro_auth_authorization_histories.rb +15 -0
  18. data/doc/dependency_decisions.yml +3 -0
  19. data/docs/CHANGELOG.md +32 -0
  20. data/docs/README.md +355 -0
  21. data/gemfiles/.bundle/config +2 -0
  22. data/gemfiles/rails_6_1.gemfile +15 -0
  23. data/gemfiles/rails_6_1.gemfile.lock +287 -0
  24. data/gemfiles/rails_7_0.gemfile +15 -0
  25. data/gemfiles/rails_7_0.gemfile.lock +286 -0
  26. data/gemfiles/rails_7_1.gemfile +15 -0
  27. data/gemfiles/rails_7_1.gemfile.lock +337 -0
  28. data/lib/consent/ability.rb +113 -4
  29. data/lib/consent/dsl.rb +1 -8
  30. data/lib/consent/{railtie.rb → engine.rb} +11 -8
  31. data/lib/consent/model_additions.rb +64 -0
  32. data/lib/consent/permission_migration.rb +139 -0
  33. data/lib/consent/reloader.rb +6 -5
  34. data/lib/consent/rspec/consent_action.rb +7 -7
  35. data/lib/consent/rspec/consent_view.rb +10 -14
  36. data/lib/consent/rspec.rb +3 -3
  37. data/lib/consent/subject_coder.rb +39 -0
  38. data/lib/consent/symbol_adapter.rb +18 -0
  39. data/lib/consent/version.rb +1 -1
  40. data/lib/consent.rb +25 -13
  41. data/lib/generators/consent/permissions_generator.rb +5 -5
  42. data/mkdocs.yml +6 -0
  43. data/renovate.json +15 -2
  44. metadata +126 -37
  45. data/.rspec +0 -2
  46. data/.ruby-version +0 -1
  47. data/.travis.yml +0 -20
  48. data/LICENSE +0 -21
  49. data/README.md +0 -252
  50. data/TODO.md +0 -1
@@ -0,0 +1,337 @@
1
+ PATH
2
+ remote: ../../rubocop-powerhome
3
+ specs:
4
+ rubocop-powerhome (0.5.3)
5
+ rubocop (= 1.66.1)
6
+ rubocop-performance
7
+ rubocop-rails
8
+ rubocop-rake
9
+ rubocop-rspec
10
+
11
+ PATH
12
+ remote: ..
13
+ specs:
14
+ consent (2.1.0)
15
+ cancancan (= 3.2.1)
16
+
17
+ GEM
18
+ remote: https://rubygems.org/
19
+ specs:
20
+ actioncable (7.1.3.2)
21
+ actionpack (= 7.1.3.2)
22
+ activesupport (= 7.1.3.2)
23
+ nio4r (~> 2.0)
24
+ websocket-driver (>= 0.6.1)
25
+ zeitwerk (~> 2.6)
26
+ actionmailbox (7.1.3.2)
27
+ actionpack (= 7.1.3.2)
28
+ activejob (= 7.1.3.2)
29
+ activerecord (= 7.1.3.2)
30
+ activestorage (= 7.1.3.2)
31
+ activesupport (= 7.1.3.2)
32
+ mail (>= 2.7.1)
33
+ net-imap
34
+ net-pop
35
+ net-smtp
36
+ actionmailer (7.1.3.2)
37
+ actionpack (= 7.1.3.2)
38
+ actionview (= 7.1.3.2)
39
+ activejob (= 7.1.3.2)
40
+ activesupport (= 7.1.3.2)
41
+ mail (~> 2.5, >= 2.5.4)
42
+ net-imap
43
+ net-pop
44
+ net-smtp
45
+ rails-dom-testing (~> 2.2)
46
+ actionpack (7.1.3.2)
47
+ actionview (= 7.1.3.2)
48
+ activesupport (= 7.1.3.2)
49
+ nokogiri (>= 1.8.5)
50
+ racc
51
+ rack (>= 2.2.4)
52
+ rack-session (>= 1.0.1)
53
+ rack-test (>= 0.6.3)
54
+ rails-dom-testing (~> 2.2)
55
+ rails-html-sanitizer (~> 1.6)
56
+ actiontext (7.1.3.2)
57
+ actionpack (= 7.1.3.2)
58
+ activerecord (= 7.1.3.2)
59
+ activestorage (= 7.1.3.2)
60
+ activesupport (= 7.1.3.2)
61
+ globalid (>= 0.6.0)
62
+ nokogiri (>= 1.8.5)
63
+ actionview (7.1.3.2)
64
+ activesupport (= 7.1.3.2)
65
+ builder (~> 3.1)
66
+ erubi (~> 1.11)
67
+ rails-dom-testing (~> 2.2)
68
+ rails-html-sanitizer (~> 1.6)
69
+ activejob (7.1.3.2)
70
+ activesupport (= 7.1.3.2)
71
+ globalid (>= 0.3.6)
72
+ activemodel (7.1.3.2)
73
+ activesupport (= 7.1.3.2)
74
+ activerecord (7.1.3.2)
75
+ activemodel (= 7.1.3.2)
76
+ activesupport (= 7.1.3.2)
77
+ timeout (>= 0.4.0)
78
+ activestorage (7.1.3.2)
79
+ actionpack (= 7.1.3.2)
80
+ activejob (= 7.1.3.2)
81
+ activerecord (= 7.1.3.2)
82
+ activesupport (= 7.1.3.2)
83
+ marcel (~> 1.0)
84
+ activesupport (7.1.3.2)
85
+ base64
86
+ bigdecimal
87
+ concurrent-ruby (~> 1.0, >= 1.0.2)
88
+ connection_pool (>= 2.2.5)
89
+ drb
90
+ i18n (>= 1.6, < 2)
91
+ minitest (>= 5.1)
92
+ mutex_m
93
+ tzinfo (~> 2.0)
94
+ appraisal (2.5.0)
95
+ bundler
96
+ rake
97
+ thor (>= 0.14.0)
98
+ ast (2.4.2)
99
+ base64 (0.2.0)
100
+ bigdecimal (3.1.9)
101
+ builder (3.3.0)
102
+ byebug (11.1.3)
103
+ cancancan (3.2.1)
104
+ coderay (1.1.3)
105
+ combustion (1.5.0)
106
+ activesupport (>= 3.0.0)
107
+ railties (>= 3.0.0)
108
+ thor (>= 0.14.6)
109
+ concurrent-ruby (1.3.5)
110
+ connection_pool (2.5.0)
111
+ crass (1.0.6)
112
+ csv (3.3.2)
113
+ date (3.4.1)
114
+ diff-lcs (1.6.0)
115
+ drb (2.2.1)
116
+ erubi (1.13.1)
117
+ globalid (1.2.1)
118
+ activesupport (>= 6.1)
119
+ i18n (1.14.7)
120
+ concurrent-ruby (~> 1.0)
121
+ io-console (0.8.0)
122
+ irb (1.15.1)
123
+ pp (>= 0.6.0)
124
+ rdoc (>= 4.0.0)
125
+ reline (>= 0.4.2)
126
+ json (2.10.1)
127
+ language_server-protocol (3.17.0.4)
128
+ license_finder (7.2.1)
129
+ bundler
130
+ csv (~> 3.2)
131
+ rubyzip (>= 1, < 3)
132
+ thor (~> 1.2)
133
+ tomlrb (>= 1.3, < 2.1)
134
+ with_env (= 1.1.0)
135
+ xml-simple (~> 1.1.9)
136
+ logger (1.6.6)
137
+ loofah (2.24.0)
138
+ crass (~> 1.0.2)
139
+ nokogiri (>= 1.12.0)
140
+ mail (2.8.1)
141
+ mini_mime (>= 0.1.1)
142
+ net-imap
143
+ net-pop
144
+ net-smtp
145
+ marcel (1.0.4)
146
+ method_source (1.1.0)
147
+ mini_mime (1.1.5)
148
+ minitest (5.25.4)
149
+ mutex_m (0.3.0)
150
+ net-imap (0.4.19)
151
+ date
152
+ net-protocol
153
+ net-pop (0.1.2)
154
+ net-protocol
155
+ net-protocol (0.2.2)
156
+ timeout
157
+ net-smtp (0.5.1)
158
+ net-protocol
159
+ nio4r (2.7.4)
160
+ nokogiri (1.17.2-aarch64-linux)
161
+ racc (~> 1.4)
162
+ nokogiri (1.17.2-arm-linux)
163
+ racc (~> 1.4)
164
+ nokogiri (1.17.2-arm64-darwin)
165
+ racc (~> 1.4)
166
+ nokogiri (1.17.2-x86_64-darwin)
167
+ racc (~> 1.4)
168
+ nokogiri (1.17.2-x86_64-linux)
169
+ racc (~> 1.4)
170
+ parallel (1.26.3)
171
+ parser (3.3.7.1)
172
+ ast (~> 2.4.1)
173
+ racc
174
+ pp (0.6.2)
175
+ prettyprint
176
+ prettyprint (0.2.0)
177
+ pry (0.14.2)
178
+ coderay (~> 1.1)
179
+ method_source (~> 1.0)
180
+ pry-byebug (3.10.1)
181
+ byebug (~> 11.0)
182
+ pry (>= 0.13, < 0.15)
183
+ psych (5.2.3)
184
+ date
185
+ stringio
186
+ racc (1.8.1)
187
+ rack (3.1.10)
188
+ rack-session (2.1.0)
189
+ base64 (>= 0.1.0)
190
+ rack (>= 3.0.0)
191
+ rack-test (2.2.0)
192
+ rack (>= 1.3)
193
+ rackup (2.2.1)
194
+ rack (>= 3)
195
+ rails (7.1.3.2)
196
+ actioncable (= 7.1.3.2)
197
+ actionmailbox (= 7.1.3.2)
198
+ actionmailer (= 7.1.3.2)
199
+ actionpack (= 7.1.3.2)
200
+ actiontext (= 7.1.3.2)
201
+ actionview (= 7.1.3.2)
202
+ activejob (= 7.1.3.2)
203
+ activemodel (= 7.1.3.2)
204
+ activerecord (= 7.1.3.2)
205
+ activestorage (= 7.1.3.2)
206
+ activesupport (= 7.1.3.2)
207
+ bundler (>= 1.15.0)
208
+ railties (= 7.1.3.2)
209
+ rails-dom-testing (2.2.0)
210
+ activesupport (>= 5.0.0)
211
+ minitest
212
+ nokogiri (>= 1.6)
213
+ rails-html-sanitizer (1.6.2)
214
+ loofah (~> 2.21)
215
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
216
+ railties (7.1.3.2)
217
+ actionpack (= 7.1.3.2)
218
+ activesupport (= 7.1.3.2)
219
+ irb
220
+ rackup (>= 1.0.0)
221
+ rake (>= 12.2)
222
+ thor (~> 1.0, >= 1.2.2)
223
+ zeitwerk (~> 2.6)
224
+ rainbow (3.1.1)
225
+ rake (13.2.1)
226
+ rdoc (6.12.0)
227
+ psych (>= 4.0.0)
228
+ regexp_parser (2.10.0)
229
+ reline (0.6.0)
230
+ io-console (~> 0.5)
231
+ rexml (3.4.1)
232
+ rspec (3.13.0)
233
+ rspec-core (~> 3.13.0)
234
+ rspec-expectations (~> 3.13.0)
235
+ rspec-mocks (~> 3.13.0)
236
+ rspec-core (3.13.3)
237
+ rspec-support (~> 3.13.0)
238
+ rspec-expectations (3.13.3)
239
+ diff-lcs (>= 1.2.0, < 2.0)
240
+ rspec-support (~> 3.13.0)
241
+ rspec-mocks (3.13.2)
242
+ diff-lcs (>= 1.2.0, < 2.0)
243
+ rspec-support (~> 3.13.0)
244
+ rspec-rails (6.1.5)
245
+ actionpack (>= 6.1)
246
+ activesupport (>= 6.1)
247
+ railties (>= 6.1)
248
+ rspec-core (~> 3.13)
249
+ rspec-expectations (~> 3.13)
250
+ rspec-mocks (~> 3.13)
251
+ rspec-support (~> 3.13)
252
+ rspec-support (3.13.2)
253
+ rubocop (1.66.1)
254
+ json (~> 2.3)
255
+ language_server-protocol (>= 3.17.0)
256
+ parallel (~> 1.10)
257
+ parser (>= 3.3.0.2)
258
+ rainbow (>= 2.2.2, < 4.0)
259
+ regexp_parser (>= 2.4, < 3.0)
260
+ rubocop-ast (>= 1.32.2, < 2.0)
261
+ ruby-progressbar (~> 1.7)
262
+ unicode-display_width (>= 2.4.0, < 3.0)
263
+ rubocop-ast (1.38.0)
264
+ parser (>= 3.3.1.0)
265
+ rubocop-performance (1.23.1)
266
+ rubocop (>= 1.48.1, < 2.0)
267
+ rubocop-ast (>= 1.31.1, < 2.0)
268
+ rubocop-rails (2.29.1)
269
+ activesupport (>= 4.2.0)
270
+ rack (>= 1.1)
271
+ rubocop (>= 1.52.0, < 2.0)
272
+ rubocop-ast (>= 1.31.1, < 2.0)
273
+ rubocop-rake (0.6.0)
274
+ rubocop (~> 1.0)
275
+ rubocop-rspec (3.4.0)
276
+ rubocop (~> 1.61)
277
+ ruby-progressbar (1.13.0)
278
+ rubyzip (2.4.1)
279
+ sqlite3 (1.7.3-aarch64-linux)
280
+ sqlite3 (1.7.3-arm-linux)
281
+ sqlite3 (1.7.3-arm64-darwin)
282
+ sqlite3 (1.7.3-x86_64-darwin)
283
+ sqlite3 (1.7.3-x86_64-linux)
284
+ stringio (3.1.3)
285
+ thor (1.3.2)
286
+ timeout (0.4.3)
287
+ tomlrb (2.0.3)
288
+ tzinfo (2.0.6)
289
+ concurrent-ruby (~> 1.0)
290
+ unicode-display_width (2.6.0)
291
+ websocket-driver (0.7.7)
292
+ base64
293
+ websocket-extensions (>= 0.1.0)
294
+ websocket-extensions (0.1.5)
295
+ with_env (1.1.0)
296
+ xml-simple (1.1.9)
297
+ rexml
298
+ zeitwerk (2.6.18)
299
+
300
+ PLATFORMS
301
+ aarch64-linux
302
+ aarch64-linux-gnu
303
+ aarch64-linux-musl
304
+ arm-linux
305
+ arm-linux-gnu
306
+ arm-linux-musl
307
+ arm64-darwin
308
+ x86_64-darwin
309
+ x86_64-linux
310
+ x86_64-linux-gnu
311
+ x86_64-linux-musl
312
+
313
+ DEPENDENCIES
314
+ activerecord (>= 5)
315
+ appraisal (~> 2.5.0)
316
+ base64
317
+ bigdecimal
318
+ bundler (~> 2.1)
319
+ combustion (~> 1.3)
320
+ consent!
321
+ license_finder (>= 7.0)
322
+ logger
323
+ mutex_m
324
+ net-imap (< 0.5.0)
325
+ nokogiri (< 1.18)
326
+ pry (>= 0.14.2)
327
+ pry-byebug (= 3.10.1)
328
+ rails (= 7.1.3.2)
329
+ rake (~> 13)
330
+ rspec (~> 3.0)
331
+ rspec-rails (~> 6.1.5)
332
+ rubocop-powerhome!
333
+ sqlite3 (~> 1.7.3)
334
+ zeitwerk (< 2.7.0)
335
+
336
+ BUNDLED WITH
337
+ 2.5.23
@@ -1,15 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consent
4
- # Defines a CanCan(Can)::Ability class based on a permissions hash
4
+ #
5
+ # Defines a CanCan(Can)::Ability class based on Consent::Permissions
6
+ #
5
7
  class Ability
6
8
  include CanCan::Ability
7
9
 
8
- def initialize(*args, apply_defaults: true)
9
- @context = *args
10
+ #
11
+ # Initialize a Consent::Ability consenting all the given permissions.
12
+ #
13
+ # When super_user is set to true, it grants `:manage :all`, which is understood by
14
+ # CanCan as a keyword to allow everything with no restrictions.
15
+ #
16
+ # If apply_defaults is set to true, Consent::Ability will grant the default views
17
+ # defined in the permissions.
18
+ #
19
+ # I.e.:
20
+ #
21
+ # Consent.define Project, 'Projects' do
22
+ # view :department, "User's department only" do |user|
23
+ # { department_id: user.id }
24
+ # end
25
+ # view :self, "User's own projects" do |user|
26
+ # { user_id: user.id }
27
+ # end
28
+ #
29
+ # action :close, views: %i[department self], default_view: :self
30
+ # end
31
+ #
32
+ # Consent::Ability.new(user, permissions: user&.permissions,
33
+ # super_user: user&.super_user?,
34
+ # apply_defaults: user.present?)
35
+ #
36
+ # @param [*] *context the view context, usually the user and some additional information
37
+ # @param [Array<Consent::Permission>] permissions the list of permissions to grant
38
+ # @param [Boolean] super_user whether Consent should grant :manage :all
39
+ # @param [Boolean] apply_defaults whether Consent should grant default views
40
+ #
41
+ def initialize(*context, permissions: nil, super_user: false, apply_defaults: true)
42
+ @context = *context
43
+
10
44
  apply_defaults! if apply_defaults
45
+ can :manage, :all if super_user
46
+
47
+ permissions&.each do |permission|
48
+ consent(**permission.slice(:subject, :action, :view).symbolize_keys)
49
+ end
11
50
  end
12
51
 
52
+ # Consents a subject/action/view to the ability
53
+ #
54
+ # `consent!` will add a `can` permission to the ability based on the
55
+ # view rules defined in the Consent definitions.
56
+ #
57
+ # @param [Class,Symbol] subject the target subject of the action
58
+ # @param [Symbol] action the action being granted on the subject
59
+ # @param [Symbol,nil] view the conditions/rules on which the action is granted
60
+ # @raises Consent::ViewNotFound when the view key doesn't exist in the context
61
+ #
13
62
  def consent!(subject: nil, action: nil, view: nil)
14
63
  view = case view
15
64
  when Consent::View
@@ -24,13 +73,55 @@ module Consent
24
73
  )
25
74
  end
26
75
 
76
+ # Consents a subject/action/view to the ability
77
+ #
78
+ # @see ::Consent::Ability#consent
79
+ #
27
80
  def consent(**kwargs)
28
81
  consent!(**kwargs)
29
82
  rescue Consent::ViewNotFound
30
83
  nil
31
84
  end
32
85
 
33
- private
86
+ # Returns a hash where the keys are the given permissions, and the values
87
+ # are either true or false, representing their ability to perform the given
88
+ # permision
89
+ #
90
+ # @param [Array<String>,String,nil] permissions an array of the requested permissions
91
+ # @return [Hash<String,Boolean>] the hash with the results
92
+ def to_h(permissions = nil)
93
+ Array(permissions).reduce({}) do |result, permission|
94
+ result.merge permission => can?(permission)
95
+ end
96
+ end
97
+
98
+ # Check if the user has permission to perform a given action on an object.
99
+ #
100
+ # can? :destroy, @project
101
+ #
102
+ # You can also pass the class instead of an instance (if you don't have one handy).
103
+ #
104
+ # can? :create, Project
105
+ #
106
+ # You can also check with string form of the permission:
107
+ #
108
+ # can? "project/create"
109
+ #
110
+ # For more info, check the documentation of [CanCan::Ability]
111
+ def can?(action_or_pair, subject = nil, *args)
112
+ action, subject = extract_action_subject(action_or_pair, subject)
113
+ super(action, subject, *args)
114
+ end
115
+
116
+ # @private
117
+ def relation_model_adapter(model_class, action_or_pair, subject, relation)
118
+ action, subject = extract_action_subject(action_or_pair, subject)
119
+ ::CanCan::ModelAdapters::AbstractAdapter
120
+ .adapter_class(model_class)
121
+ .new(model_class, relation_rules(model_class, action, subject, relation))
122
+ end
123
+
124
+ private
34
125
 
35
126
  def apply_defaults!
36
127
  Consent.subjects.each do |subject|
@@ -45,5 +136,23 @@ module Consent
45
136
  end
46
137
  end
47
138
  end
139
+
140
+ def relation_rules(model_class, action, subject, relation)
141
+ relevant_rules(action, subject).map do |rule|
142
+ unless rule.conditions.is_a?(Hash)
143
+ raise ::CanCan::Error, "accessible_through is only available with hash conditions"
144
+ end
145
+
146
+ conditions = rule.conditions.dig(*Array(relation))
147
+ ::CanCan::Rule.new(rule.base_behavior, action, model_class, conditions, rule.block)
148
+ end
149
+ end
150
+
151
+ def extract_action_subject(action_or_string_pair, subject)
152
+ return action_or_string_pair, subject if subject
153
+
154
+ subject_key, _, action_key = action_or_string_pair.rpartition("/")
155
+ [action_key.to_sym, Consent::SubjectCoder.load(subject_key)]
156
+ end
48
157
  end
49
158
  end
data/lib/consent/dsl.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consent
4
+ # @private
4
5
  class DSL # :nodoc:
5
6
  attr_reader :subject
6
7
 
@@ -13,14 +14,6 @@ module Consent
13
14
  DSL.build(@subject, @defaults.merge(new_defaults), &block)
14
15
  end
15
16
 
16
- # rubocop:disable Lint/UnusedBlockArgument, Security/Eval
17
- def eval_view(key, label, collection_conditions)
18
- view key, label do |user|
19
- eval(collection_conditions)
20
- end
21
- end
22
- # rubocop:enable Lint/UnusedBlockArgument, Security/Eval
23
-
24
17
  def view(key, label, instance = nil, collection = nil, &block)
25
18
  collection ||= block
26
19
  @subject.views[key] = View.new(key, label, instance, collection)
@@ -1,26 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'consent/reloader'
3
+ require "consent"
4
4
 
5
5
  module Consent
6
6
  # Plugs consent permission load to the Rails class loading cycle
7
- class Railtie < Rails::Railtie
7
+ class Engine < Rails::Engine
8
8
  config.before_configuration do |app|
9
- default_path = app.root.join('app', 'permissions')
10
- config.consent = Consent::Reloader.new(
11
- default_path,
12
- ActiveSupport::Dependencies.mechanism
13
- )
9
+ default_path = app.root.join("app", "permissions")
10
+ config.consent = Consent::Reloader.new(default_path)
14
11
  end
15
12
 
16
13
  config.after_initialize do |app|
17
14
  app.config.consent.execute
18
15
  end
19
16
 
20
- initializer 'initialize consent permissions reloading' do |app|
17
+ initializer "consent.reloader" do |app|
21
18
  app.reloaders << config.consent
22
19
  ActiveSupport::Dependencies.autoload_paths -= config.consent.paths
23
20
  config.to_prepare { app.config.consent.execute }
24
21
  end
22
+
23
+ initializer "consent.accessible_through" do
24
+ ActiveSupport.on_load(:active_record) do
25
+ include Consent::ModelAdditions
26
+ end
27
+ end
25
28
  end
26
29
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consent
4
+ #
5
+ # Accessible Through logic
6
+ #
7
+ # This module adds the accessible_through class method to a model.
8
+ # It is included in the activerecord base classes by Consent::Engine.
9
+ #
10
+ # @see Consent::ModelAdditions::ClassMethods#accessible_through
11
+ #
12
+ module ModelAdditions
13
+ module ClassMethods
14
+ # Provides a scope within the model to find instances of the model that are accessible
15
+ # by the given ability through a given relation in the main subject
16
+ #
17
+ # I.E.:
18
+ #
19
+ # Given the following scenario
20
+ #
21
+ # class User
22
+ # belongs_to :territory
23
+ # end
24
+ #
25
+ # Consent.define User, "User permissions" do
26
+ # view :territory do |user|
27
+ # { territory: { id: user.territory_id } }
28
+ # end
29
+ # view :visible_territories do |user|
30
+ # { territory: { id: user.visible_territory_ids } }
31
+ # end
32
+ #
33
+ # action :contact, views: %i[all no_access territory visible_territories]
34
+ # end
35
+ #
36
+ # This would give you a list of territories that the given ability can
37
+ # contact their users:
38
+ #
39
+ # > user = User.new(territory_id: 13, visible_territory_ids: [2, 3, 4])
40
+ # > ability = Consent::Ability.new(user.to_session_user)
41
+ # > ability.consent view: :territory, action: :contact, subject: User
42
+ # > Territory.accessible_through(ability, :contact, User).to_sql
43
+ # => SELECT * FROM territories WHERE id = 13
44
+ # > ability.consent view: :visible_territories, action: :contact, subject: User
45
+ # > Territory.accessible_through(ability, :contact, User).to_sql
46
+ # => SELECT * FROM territories WHERE ((id = 13) OR (id IN (2, 3, 4)))
47
+ #
48
+ # @param ability [Consent::Ability] ability performing the query
49
+ # @param action_or_pair [Symbol,String] the name of the action or a subject/action pair
50
+ # @param subject [Class,Symbol,nil] the subject in which the action is, when action_or_pair is just the action
51
+ # @param relation [Symbol,Array<Symbol>] the relation or path to the relation
52
+ #
53
+ def accessible_through(ability, action_or_pair, subject = nil, relation: nil)
54
+ relation ||= model_name.element.to_sym
55
+ ability.relation_model_adapter(self, action_or_pair, subject, relation)
56
+ .database_records
57
+ end
58
+ end
59
+
60
+ def self.included(base)
61
+ base.extend ClassMethods
62
+ end
63
+ end
64
+ end