consent 1.0.1 → 2.1.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.
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