cancancan 2.3.0 → 3.3.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/cancancan.gemspec +6 -5
  3. data/init.rb +2 -0
  4. data/lib/cancan/ability/actions.rb +2 -0
  5. data/lib/cancan/ability/rules.rb +19 -8
  6. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/lib/cancan/ability.rb +54 -24
  8. data/lib/cancan/class_matcher.rb +26 -0
  9. data/lib/cancan/conditions_matcher.rb +25 -12
  10. data/lib/cancan/config.rb +74 -0
  11. data/lib/cancan/controller_additions.rb +4 -1
  12. data/lib/cancan/controller_resource.rb +6 -0
  13. data/lib/cancan/controller_resource_builder.rb +2 -0
  14. data/lib/cancan/controller_resource_finder.rb +2 -0
  15. data/lib/cancan/controller_resource_loader.rb +4 -0
  16. data/lib/cancan/controller_resource_name_finder.rb +2 -0
  17. data/lib/cancan/controller_resource_sanitizer.rb +2 -0
  18. data/lib/cancan/exceptions.rb +18 -2
  19. data/lib/cancan/matchers.rb +3 -0
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +3 -1
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +26 -25
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +21 -26
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +56 -14
  24. data/lib/cancan/model_adapters/conditions_extractor.rb +3 -3
  25. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  26. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  27. data/lib/cancan/model_adapters/sti_normalizer.rb +39 -0
  28. data/lib/cancan/model_additions.rb +6 -2
  29. data/lib/cancan/parameter_validators.rb +9 -0
  30. data/lib/cancan/relevant.rb +29 -0
  31. data/lib/cancan/rule.rb +67 -23
  32. data/lib/cancan/rules_compressor.rb +3 -0
  33. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  34. data/lib/cancan/version.rb +3 -1
  35. data/lib/cancan.rb +6 -0
  36. data/lib/cancancan.rb +2 -0
  37. data/lib/generators/cancan/ability/ability_generator.rb +3 -1
  38. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  39. metadata +37 -30
  40. data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f63f1d6ab266068caab6e7baf2b628ae72a7e03a134116eda91473861b9df359
4
- data.tar.gz: fbf6337f058ece9a2f01c990a55398489e0ea56888f0a5ce3b9f8d5e203c31ae
3
+ metadata.gz: c4498ac94e1994faa4da80dc957d8c9564433d991048774f9ac2f051e60de580
4
+ data.tar.gz: 74209123c4c49adcd1d2d81df1de61c5f8cc2f243fdcdb4d01d3e41731e4c266
5
5
  SHA512:
6
- metadata.gz: 24fcc98ce0592b263add65cf0bc75a7020d75777fea7c6902216f97bbc9c13a36b4d379194a142cd8a5e5a24dc1ae82c82a61d6d99143c30a9afe11133048528
7
- data.tar.gz: 8873b440698a941314f67a748db86c2abe33e89417ae54d9f35769c29f904edc9eff5736d0bf55d53badc494b9bca9e0ac1761c1920067238628d23dc8e0c643
6
+ metadata.gz: eb7774650d12a7073d09bb713f7eedfd7d376689f6d6bb842620b325a814720172f4fec900705bcc0dd3bd90818a88d2ab7e3904ea3a5d004533e13b4bed1c4c
7
+ data.tar.gz: a7a6fff07fbd7d52816dd960d00ae0baea7d50c5ed41d0abf32ac3a0c2b6bc3c557a4309944717b57aad785ad7dc394870c079b6a820b62798a373a9d9f8c2b0
data/cancancan.gemspec CHANGED
@@ -1,6 +1,6 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'cancan/version'
6
6
 
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.authors = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
11
11
  s.email = 'alessandro.rodi@renuo.ch'
12
12
  s.homepage = 'https://github.com/CanCanCommunity/cancancan'
13
+ s.metadata = { 'funding_uri' => 'https://github.com/sponsors/coorasse' }
13
14
  s.summary = 'Simple authorization solution for Rails.'
14
15
  s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.'
15
16
  s.platform = Gem::Platform::RUBY
@@ -20,9 +21,9 @@ Gem::Specification.new do |s|
20
21
 
21
22
  s.required_ruby_version = '>= 2.2.0'
22
23
 
23
- s.add_development_dependency 'bundler', '~> 1.3'
24
- s.add_development_dependency 'rubocop', '~> 0.48.1'
24
+ s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
25
+ s.add_development_dependency 'bundler', '~> 2.0'
25
26
  s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
26
27
  s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
27
- s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
28
+ s.add_development_dependency 'rubocop', '~> 0.63.1'
28
29
  end
data/init.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cancan'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module Ability
3
5
  module Actions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module Ability
3
5
  module Rules
@@ -17,12 +19,13 @@ module CanCan
17
19
  end
18
20
 
19
21
  def add_rule_to_index(rule, position)
20
- @rules_index ||= Hash.new { |h, k| h[k] = [] }
22
+ @rules_index ||= {}
21
23
 
22
24
  subjects = rule.subjects.compact
23
25
  subjects << :all if subjects.empty?
24
26
 
25
27
  subjects.each do |subject|
28
+ @rules_index[subject] ||= []
26
29
  @rules_index[subject] << position
27
30
  end
28
31
  end
@@ -31,6 +34,7 @@ module CanCan
31
34
  # This does not take into consideration any hash conditions or block statements
32
35
  def relevant_rules(action, subject)
33
36
  return [] unless @rules
37
+
34
38
  relevant = possible_relevant_rules(subject).select do |rule|
35
39
  rule.expanded_actions = expand_actions(rule.actions)
36
40
  rule.relevant? action, subject
@@ -45,7 +49,9 @@ module CanCan
45
49
  rules
46
50
  else
47
51
  positions = @rules_index.values_at(subject, *alternative_subjects(subject))
48
- positions.flatten!.sort!
52
+ positions.compact!
53
+ positions.flatten!
54
+ positions.sort!
49
55
  positions.map { |i| @rules[i] }
50
56
  end
51
57
  end
@@ -53,19 +59,23 @@ module CanCan
53
59
  def relevant_rules_for_match(action, subject)
54
60
  relevant_rules(action, subject).each do |rule|
55
61
  next unless rule.only_raw_sql?
62
+
56
63
  raise Error,
57
64
  "The can? and cannot? call cannot be used with a raw sql 'can' definition."\
58
- " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
65
+ " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
59
66
  end
60
67
  end
61
68
 
62
69
  def relevant_rules_for_query(action, subject)
63
- relevant_rules(action, subject).each do |rule|
64
- if rule.only_block?
65
- raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
66
- " The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
67
- end
70
+ rules = relevant_rules(action, subject).reject do |rule|
71
+ # reject 'cannot' rules with attributes when doing queries
72
+ rule.base_behavior == false && rule.attributes.present?
73
+ end
74
+ if rules.any?(&:only_block?)
75
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
76
+ "The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
68
77
  end
78
+ rules
69
79
  end
70
80
 
71
81
  # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
@@ -75,6 +85,7 @@ module CanCan
75
85
  (first_can_in_group = -1) && next unless rule.base_behavior
76
86
  (first_can_in_group = i) && next if first_can_in_group == -1
77
87
  next unless rule.subjects == [:all]
88
+
78
89
  rules[i] = rules[first_can_in_group]
79
90
  rules[first_can_in_group] = rule
80
91
  first_can_in_group += 1
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module Ability
5
+ module StrongParameterSupport
6
+ # Returns an array of attributes suitable for use with strong parameters
7
+ #
8
+ # Note: reversing the relevant rules is important. Normal order means that 'cannot'
9
+ # rules will come before 'can' rules. However, you can't remove attributes before
10
+ # they are added. The 'reverse' is so that attributes will be added before the
11
+ # 'cannot' rules remove them.
12
+ def permitted_attributes(action, subject)
13
+ relevant_rules(action, subject)
14
+ .reverse
15
+ .select { |rule| rule.matches_conditions? action, subject }
16
+ .each_with_object(Set.new) do |rule, set|
17
+ attributes = get_attributes(rule, subject)
18
+ # add attributes for 'can', remove them for 'cannot'
19
+ rule.base_behavior ? set.merge(attributes) : set.subtract(attributes)
20
+ end.to_a
21
+ end
22
+
23
+ private
24
+
25
+ def subject_class?(subject)
26
+ klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
27
+ [Class, Module].include? klass
28
+ end
29
+
30
+ def get_attributes(rule, subject)
31
+ klass = subject_class?(subject) ? subject : subject.class
32
+ # empty attributes is an 'all'
33
+ if rule.attributes.empty? && klass < ActiveRecord::Base
34
+ klass.column_names.map(&:to_sym) - Array(klass.primary_key)
35
+ else
36
+ rule.attributes
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'ability/rules.rb'
2
4
  require_relative 'ability/actions.rb'
5
+ require_relative 'unauthorized_message_resolver.rb'
6
+ require_relative 'ability/strong_parameter_support'
7
+
3
8
  module CanCan
4
9
  # This module is designed to be included into an Ability class. This will
5
10
  # provide the "can" methods for defining and checking abilities.
@@ -19,6 +24,8 @@ module CanCan
19
24
  module Ability
20
25
  include CanCan::Ability::Rules
21
26
  include CanCan::Ability::Actions
27
+ include CanCan::UnauthorizedMessageResolver
28
+ include StrongParameterSupport
22
29
 
23
30
  # Check if the user has permission to perform a given action on an object.
24
31
  #
@@ -64,10 +71,10 @@ module CanCan
64
71
  # end
65
72
  #
66
73
  # Also see the RSpec Matchers to aid in testing.
67
- def can?(action, subject, *extra_args)
74
+ def can?(action, subject, attribute = nil, *extra_args)
68
75
  match = extract_subjects(subject).lazy.map do |a_subject|
69
76
  relevant_rules_for_match(action, a_subject).detect do |rule|
70
- rule.matches_conditions?(action, a_subject, extra_args)
77
+ rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute)
71
78
  end
72
79
  end.reject(&:nil?).first
73
80
  match ? match.base_behavior : false
@@ -134,8 +141,8 @@ module CanCan
134
141
  # # check the database and return true/false
135
142
  # end
136
143
  #
137
- def can(action = nil, subject = nil, conditions = nil, &block)
138
- add_rule(Rule.new(true, action, subject, conditions, block))
144
+ def can(action = nil, subject = nil, *attributes_and_conditions, &block)
145
+ add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
139
146
  end
140
147
 
141
148
  # Defines an ability which cannot be done. Accepts the same arguments as "can".
@@ -150,8 +157,8 @@ module CanCan
150
157
  # product.invisible?
151
158
  # end
152
159
  #
153
- def cannot(action = nil, subject = nil, conditions = nil, &block)
154
- add_rule(Rule.new(false, action, subject, conditions, block))
160
+ def cannot(action = nil, subject = nil, *attributes_and_conditions, &block)
161
+ add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block))
155
162
  end
156
163
 
157
164
  # User shouldn't specify targets with names of real actions or it will cause Seg fault
@@ -167,10 +174,7 @@ module CanCan
167
174
 
168
175
  # See ControllerAdditions#authorize! for documentation.
169
176
  def authorize!(action, subject, *args)
170
- message = nil
171
- if args.last.is_a?(Hash) && args.last.key?(:message)
172
- message = args.pop[:message]
173
- end
177
+ message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
174
178
  if cannot?(action, subject, *args)
175
179
  message ||= unauthorized_message(action, subject)
176
180
  raise AccessDenied.new(message, action, subject, args)
@@ -178,14 +182,6 @@ module CanCan
178
182
  subject
179
183
  end
180
184
 
181
- def unauthorized_message(action, subject)
182
- keys = unauthorized_message_keys(action, subject)
183
- variables = { action: action.to_s }
184
- variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
185
- message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
186
- message.blank? ? nil : message
187
- end
188
-
189
185
  def attributes_for(action, subject)
190
186
  attributes = {}
191
187
  relevant_rules(action, subject).map do |rule|
@@ -202,12 +198,13 @@ module CanCan
202
198
  relevant_rules(action, subject).any?(&:only_raw_sql?)
203
199
  end
204
200
 
205
- # Copies all rules of the given +CanCan::Ability+ and adds them to +self+.
201
+ # Copies all rules and aliased actions of the given +CanCan::Ability+ and adds them to +self+.
206
202
  # class ReadAbility
207
203
  # include CanCan::Ability
208
204
  #
209
205
  # def initialize
210
206
  # can :read, User
207
+ # alias_action :show, :index, to: :see
211
208
  # end
212
209
  # end
213
210
  #
@@ -216,6 +213,7 @@ module CanCan
216
213
  #
217
214
  # def initialize
218
215
  # can :edit, User
216
+ # alias_action :create, :update, to: :modify
219
217
  # end
220
218
  # end
221
219
  #
@@ -223,11 +221,35 @@ module CanCan
223
221
  # read_ability.can? :edit, User.new #=> false
224
222
  # read_ability.merge(WritingAbility.new)
225
223
  # read_ability.can? :edit, User.new #=> true
224
+ # read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]]
225
+ #
226
+ # If there are collisions when merging the +aliased_actions+, the actions on +self+ will be
227
+ # overwritten.
226
228
  #
229
+ # class ReadAbility
230
+ # include CanCan::Ability
231
+ #
232
+ # def initialize
233
+ # alias_action :show, :index, to: :see
234
+ # end
235
+ # end
236
+ #
237
+ # class ShowAbility
238
+ # include CanCan::Ability
239
+ #
240
+ # def initialize
241
+ # alias_action :show, to: :see
242
+ # end
243
+ # end
244
+ #
245
+ # read_ability = ReadAbility.new
246
+ # read_ability.merge(ShowAbility)
247
+ # read_ability.aliased_actions #=> [:see => [:show]]
227
248
  def merge(ability)
228
249
  ability.rules.each do |rule|
229
250
  add_rule(rule.dup)
230
251
  end
252
+ @aliased_actions = aliased_actions.merge(ability.aliased_actions)
231
253
  self
232
254
  end
233
255
 
@@ -239,10 +261,13 @@ module CanCan
239
261
  #
240
262
  # Where can_hash and cannot_hash are formatted thusly:
241
263
  # {
242
- # action: array_of_objects
264
+ # action: { subject: [attributes] }
243
265
  # }
244
266
  def permissions
245
- permissions_list = { can: {}, cannot: {} }
267
+ permissions_list = {
268
+ can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } },
269
+ cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }
270
+ }
246
271
  rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
247
272
  permissions_list
248
273
  end
@@ -250,8 +275,9 @@ module CanCan
250
275
  def extract_rule_in_permissions(permissions_list, rule)
251
276
  expand_actions(rule.actions).each do |action|
252
277
  container = rule.base_behavior ? :can : :cannot
253
- permissions_list[container][action] ||= []
254
- permissions_list[container][action] += rule.subjects.map(&:to_s)
278
+ rule.subjects.each do |subject|
279
+ permissions_list[container][action][subject.to_s] += rule.attributes
280
+ end
255
281
  end
256
282
  end
257
283
 
@@ -276,7 +302,11 @@ module CanCan
276
302
 
277
303
  def alternative_subjects(subject)
278
304
  subject = subject.class unless subject.is_a?(Module)
279
- [:all, *subject.ancestors, subject.class.to_s]
305
+ if subject.respond_to?(:subclasses) && defined?(ActiveRecord::Base) && subject < ActiveRecord::Base
306
+ [:all, *(subject.ancestors + subject.subclasses), subject.class.to_s]
307
+ else
308
+ [:all, *subject.ancestors, subject.class.to_s]
309
+ end
280
310
  end
281
311
  end
282
312
  end
@@ -0,0 +1,26 @@
1
+ # This class is responsible for matching classes and their subclasses as well as
2
+ # upmatching classes to their ancestors.
3
+ # This is used to generate sti connections
4
+ class SubjectClassMatcher
5
+ def self.matches_subject_class?(subjects, subject)
6
+ subjects.any? do |sub|
7
+ has_subclasses = subject.respond_to?(:subclasses)
8
+ matching_class_check(subject, sub, has_subclasses)
9
+ end
10
+ end
11
+
12
+ def self.matching_class_check(subject, sub, has_subclasses)
13
+ matches = matches_class_or_is_related(subject, sub)
14
+ if has_subclasses
15
+ matches || subject.subclasses.include?(sub)
16
+ else
17
+ matches
18
+ end
19
+ end
20
+
21
+ def self.matches_class_or_is_related(subject, sub)
22
+ sub.is_a?(Module) && (subject.is_a?(sub) ||
23
+ subject.class.to_s == sub.to_s ||
24
+ (subject.is_a?(Module) && subject.ancestors.include?(sub)))
25
+ end
26
+ end
@@ -1,26 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ConditionsMatcher
3
5
  # Matches the block or conditions hash
4
- def matches_conditions?(action, subject, extra_args)
6
+ def matches_conditions?(action, subject, attribute = nil, *extra_args)
5
7
  return call_block_with_all(action, subject, extra_args) if @match_all
6
- return @block.call(subject, *extra_args) if @block && !subject_class?(subject)
7
- matches_non_block_conditions(subject)
8
+ return matches_block_conditions(subject, attribute, *extra_args) if @block
9
+ return matches_non_block_conditions(subject) unless conditions_empty?
10
+
11
+ true
8
12
  end
9
13
 
10
14
  private
11
15
 
12
16
  def subject_class?(subject)
13
17
  klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
14
- klass == Class || klass == Module
18
+ [Class, Module].include? klass
19
+ end
20
+
21
+ def matches_block_conditions(subject, *extra_args)
22
+ return @base_behavior if subject_class?(subject)
23
+
24
+ @block.call(subject, *extra_args.compact)
15
25
  end
16
26
 
17
27
  def matches_non_block_conditions(subject)
18
- if @conditions.is_a?(Hash)
19
- return nested_subject_matches_conditions?(subject) if subject.class == Hash
20
- return matches_conditions_hash?(subject) unless subject_class?(subject)
21
- end
28
+ return nested_subject_matches_conditions?(subject) if subject.class == Hash
29
+ return matches_conditions_hash?(subject) unless subject_class?(subject)
30
+
22
31
  # Don't stop at "cannot" definitions when there are conditions.
23
- conditions_empty? ? true : @base_behavior
32
+ @base_behavior
24
33
  end
25
34
 
26
35
  def nested_subject_matches_conditions?(subject_hash)
@@ -34,6 +43,7 @@ module CanCan
34
43
  # matches_conditions_hash?(subject, conditions)
35
44
  def matches_conditions_hash?(subject, conditions = @conditions)
36
45
  return true if conditions.empty?
46
+
37
47
  adapter = model_adapter(subject)
38
48
 
39
49
  if adapter.override_conditions_hash_matching?(subject, conditions)
@@ -68,13 +78,13 @@ module CanCan
68
78
 
69
79
  def hash_condition_match?(attribute, value)
70
80
  if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
71
- attribute.any? { |element| matches_conditions_hash?(element, value) }
81
+ attribute.to_a.any? { |element| matches_conditions_hash?(element, value) }
72
82
  else
73
83
  attribute && matches_conditions_hash?(attribute, value)
74
84
  end
75
85
  end
76
86
 
77
- def call_block_with_all(action, subject, extra_args)
87
+ def call_block_with_all(action, subject, *extra_args)
78
88
  if subject.class == Class
79
89
  @block.call(action, subject, nil, *extra_args)
80
90
  else
@@ -87,7 +97,10 @@ module CanCan
87
97
  end
88
98
 
89
99
  def conditions_empty?
90
- @conditions == {} || @conditions.nil?
100
+ # @conditions might be an ActiveRecord::Associations::CollectionProxy
101
+ # which it's `==` implementation will fetch all records for comparison
102
+
103
+ (@conditions.is_a?(Hash) && @conditions == {}) || @conditions.nil?
91
104
  end
92
105
  end
93
106
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ def self.valid_accessible_by_strategies
5
+ strategies = [:left_join]
6
+ strategies << :subquery unless does_not_support_subquery_strategy?
7
+ strategies
8
+ end
9
+
10
+ # Determines how CanCan should build queries when calling accessible_by,
11
+ # if the query will contain a join. The default strategy is `:subquery`.
12
+ #
13
+ # # config/initializers/cancan.rb
14
+ # CanCan.accessible_by_strategy = :subquery
15
+ #
16
+ # Valid strategies are:
17
+ # - :subquery - Creates a nested query with all joins, wrapped by a
18
+ # WHERE IN query.
19
+ # - :left_join - Calls the joins directly using `left_joins`, and
20
+ # ensures records are unique using `distinct`. Note that
21
+ # `distinct` is not reliable in some cases. See
22
+ # https://github.com/CanCanCommunity/cancancan/pull/605
23
+ def self.accessible_by_strategy
24
+ return @accessible_by_strategy if @accessible_by_strategy
25
+
26
+ @accessible_by_strategy = default_accessible_by_strategy
27
+ end
28
+
29
+ def self.default_accessible_by_strategy
30
+ if does_not_support_subquery_strategy?
31
+ # see https://github.com/CanCanCommunity/cancancan/pull/655 for where this was added
32
+ # the `subquery` strategy (from https://github.com/CanCanCommunity/cancancan/pull/619
33
+ # only works in Rails 5 and higher
34
+ :left_join
35
+ else
36
+ :subquery
37
+ end
38
+ end
39
+
40
+ def self.accessible_by_strategy=(value)
41
+ validate_accessible_by_strategy!(value)
42
+
43
+ if value == :subquery && does_not_support_subquery_strategy?
44
+ raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer'
45
+ end
46
+
47
+ @accessible_by_strategy = value
48
+ end
49
+
50
+ def self.with_accessible_by_strategy(value)
51
+ return yield if value == accessible_by_strategy
52
+
53
+ validate_accessible_by_strategy!(value)
54
+
55
+ begin
56
+ strategy_was = accessible_by_strategy
57
+ @accessible_by_strategy = value
58
+ yield
59
+ ensure
60
+ @accessible_by_strategy = strategy_was
61
+ end
62
+ end
63
+
64
+ def self.validate_accessible_by_strategy!(value)
65
+ return if valid_accessible_by_strategies.include?(value)
66
+
67
+ raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}"
68
+ end
69
+
70
+ def self.does_not_support_subquery_strategy?
71
+ !defined?(CanCan::ModelAdapters::ActiveRecordAdapter) ||
72
+ CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
73
+ end
74
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  # This module is automatically included into all controllers.
3
5
  # It also makes the "can?" and "cannot?" methods available to all views.
@@ -225,7 +227,7 @@ module CanCan
225
227
  cancan_skipper[:authorize][name] = options
226
228
  end
227
229
 
228
- # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
230
+ # Add this to a controller to ensure it performs authorization through +authorize+! or +authorize_resource+ call.
229
231
  # If neither of these authorization methods are called,
230
232
  # a CanCan::AuthorizationNotPerformed exception will be raised.
231
233
  # This is normally added to the ApplicationController to ensure all controller actions do authorization.
@@ -260,6 +262,7 @@ module CanCan
260
262
  next if controller.instance_variable_defined?(:@_authorized)
261
263
  next if options[:if] && !controller.send(options[:if])
262
264
  next if options[:unless] && controller.send(options[:unless])
265
+
263
266
  raise AuthorizationNotPerformed,
264
267
  'This action failed the check_authorization because it does not authorize_resource. '\
265
268
  'Add skip_authorization_check to bypass this check.'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'controller_resource_loader.rb'
2
4
  module CanCan
3
5
  # Handle the load and authorization controller logic
@@ -34,6 +36,7 @@ module CanCan
34
36
 
35
37
  def authorize_resource
36
38
  return if skip?(:authorize)
39
+
37
40
  @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
38
41
  end
39
42
 
@@ -43,6 +46,7 @@ module CanCan
43
46
 
44
47
  def skip?(behavior)
45
48
  return false unless (options = @controller.class.cancan_skipper[behavior][@name])
49
+
46
50
  options == {} ||
47
51
  options[:except] && !action_exists_in?(options[:except]) ||
48
52
  action_exists_in?(options[:only])
@@ -90,6 +94,7 @@ module CanCan
90
94
 
91
95
  def resource_instance
92
96
  return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
97
+
93
98
  @controller.instance_variable_get("@#{instance_name}")
94
99
  end
95
100
 
@@ -99,6 +104,7 @@ module CanCan
99
104
 
100
105
  def collection_instance
101
106
  return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
107
+
102
108
  @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
103
109
  end
104
110
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceBuilder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceFinder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'controller_resource_finder.rb'
2
4
  require_relative 'controller_resource_name_finder.rb'
3
5
  require_relative 'controller_resource_builder.rb'
@@ -11,6 +13,7 @@ module CanCan
11
13
 
12
14
  def load_resource
13
15
  return if skip?(:load)
16
+
14
17
  if load_instance?
15
18
  self.resource_instance ||= load_resource_instance
16
19
  elsif load_collection?
@@ -26,6 +29,7 @@ module CanCan
26
29
 
27
30
  def resource_params_by_key(key)
28
31
  return unless @options[key] && @params.key?(extract_key(@options[key]))
32
+
29
33
  @params[extract_key(@options[key])]
30
34
  end
31
35
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceNameFinder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceSanitizer
3
5
  protected