Skip to content

Commit a1e9a08

Browse files
committed
Merge branch 'develop'
2 parents 3e9e438 + bd7158c commit a1e9a08

19 files changed

Lines changed: 442 additions & 32 deletions

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Style/Documentation:
66
Style/NonNilCheck:
77
IncludeSemanticChanges: true
88

9+
Style/RedundantInitialize:
10+
Enabled: false
11+
912
Style/EmptyMethod:
1013
Enabled: false
1114

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
## Unreleased
1+
## 3.5.0
2+
3+
* [#653](https://github.com/CanCanCommunity/cancancan/pull/653): Add support for using an nil relation as a condition. ([@ghiculescu][])
4+
* [#702](https://github.com/CanCanCommunity/cancancan/pull/702): Support scopes of STI classes as ability conditions. ([@honigc][])
5+
* [#798](https://github.com/CanCanCommunity/cancancan/pull/798): Allow disabling of rules compressor via `CanCan.rules_compressor_enabled = false`. ([@coorasse][])
6+
* [#814](https://github.com/CanCanCommunity/cancancan/pull/814): Fix issue with polymorphic associations. ([@WriterZephos][])
27

38
## 3.4.0
49

@@ -700,3 +705,5 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co
700705
[@ghiculescu]: https://github.com/ghiculescu
701706
[@mtoneil]: https://github.com/mtoneil
702707
[@Juleffel]: https://github.com/Juleffel
708+
[@honigc]: https://github.com/honigc
709+
[@WriterZephos]: https://github.com/WriterZephos

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan)
88

99
[Developer guide](./docs/README.md) |
10-
[RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) |
10+
[RDocs](https://www.rubydoc.info/github/CanCanCommunity/cancancan) |
1111
[Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) |
1212
[Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw)
1313

@@ -179,6 +179,8 @@ When first developing, you need to run `bundle install` and then `bundle exec ap
179179

180180
You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `DB='sqlite' bundle exec appraisal activerecord_5.2.2 rake`.
181181

182+
If you'd like to run a specific set of tests within a specific file or folder you can use `DB='sqlite' SPEC=path/to/file/or/folder bundle exec appraisal activerecord_5.2.2 rake`.
183+
182184
If you use RubyMine, you can run RSpec tests by configuring the RSpec configuration template like this:
183185
![rubymine_rspec.png](rubymine_rspec.png)
184186

cancancan.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ Gem::Specification.new do |s|
2525
s.add_development_dependency 'bundler', '~> 2.0'
2626
s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
2727
s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
28-
s.add_development_dependency 'rubocop', '~> 1.26'
28+
s.add_development_dependency 'rubocop', '~> 1.31.1'
2929
end

docs/friendly_id.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ If you are using [FriendlyId](https://github.com/norman/friendly_id) you will pr
44

55
You do not have to write `find_by :slug` or something like that, that is always error prone.
66

7-
You just need to create a `config/initizializers/cancancan.rb` file with:
7+
You just need to create a `config/initializers/cancancan.rb` file with:
88

99
```ruby
1010
if defined?(CanCanCan)

docs/hash_of_conditions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ An array or range can be passed to match multiple values. Here the user can only
4646
can :read, Project, priority: 1..3
4747
```
4848

49+
If you want to a negative match, you can pass in `nil`.
50+
51+
```ruby
52+
# Can read projects that don't have any members.
53+
can :read, Project, members: { id: nil }
54+
```
55+
4956
Almost anything that you can pass to a hash of conditions in ActiveRecord will work here as well.
5057

5158
## Traverse associations

docs/rules_compression.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Rules compressions
22

3-
Your rules are optimized automatically at runtime. There are a set of "rules" to optimize your rules definition and they are implemented in the `RulesCompressor` class. Here you can see how this works:
3+
Database are great on optimizing queries, but sometimes cancancan builds `joins` that might lead to slow performance.
4+
This is why your rules are optimized automatically at runtime.
5+
There are a set of "rules" to optimize your rules definition and they are implemented in the `RulesCompressor` class.
6+
You can always disable the rules compressor by setting `CanCan.rules_compressor_enabled = false` in your initializer.
7+
You can also enable/disable it on a specific check by using: `with_rules_compressor_enabled(false) { ... }`
8+
9+
Here you can see how this works:
410

511
A rule without conditions is defined as `catch_all`.
612

lib/cancan/ability/rules.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def relevant_rules_for_match(action, subject)
6161
next unless rule.only_raw_sql?
6262

6363
raise Error,
64-
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
65-
" The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
64+
"The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
65+
"The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
6666
end
6767
end
6868

@@ -72,7 +72,7 @@ def relevant_rules_for_query(action, subject)
7272
rule.base_behavior == false && rule.attributes.present?
7373
end
7474
if rules.any?(&:only_block?)
75-
raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
75+
raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
7676
"The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
7777
end
7878
rules

lib/cancan/conditions_matcher.rb

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ def subject_class?(subject)
1818
[Class, Module].include? klass
1919
end
2020

21-
def matches_block_conditions(subject, *extra_args)
21+
def matches_block_conditions(subject, attribute, *extra_args)
2222
return @base_behavior if subject_class?(subject)
2323

24-
@block.call(subject, *extra_args.compact)
24+
if attribute
25+
@block.call(subject, attribute, *extra_args)
26+
else
27+
@block.call(subject, *extra_args)
28+
end
2529
end
2630

2731
def matches_non_block_conditions(subject)
@@ -35,11 +39,13 @@ def matches_non_block_conditions(subject)
3539
def nested_subject_matches_conditions?(subject_hash)
3640
parent, child = subject_hash.first
3741

38-
matches_base_parent_conditions = matches_conditions_hash?(parent,
39-
@conditions[parent.class.name.downcase.to_sym] || {})
40-
4142
adapter = model_adapter(parent)
4243

44+
parent_condition_name = adapter.parent_condition_name(parent, child)
45+
46+
matches_base_parent_conditions = matches_conditions_hash?(parent,
47+
@conditions[parent_condition_name] || {})
48+
4349
matches_base_parent_conditions &&
4450
(!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) ||
4551
adapter.nested_subject_matches_conditions?(parent, child, @conditions))
@@ -63,16 +69,15 @@ def matches_conditions_hash?(subject, conditions = @conditions)
6369

6470
def matches_all_conditions?(adapter, subject, conditions)
6571
if conditions.is_a?(Hash)
66-
matches_hash_conditions(adapter, subject, conditions)
72+
matches_hash_conditions?(adapter, subject, conditions)
6773
elsif conditions.respond_to?(:include?)
6874
conditions.include?(subject)
6975
else
70-
puts "does #{subject} match #{conditions}?"
7176
subject == conditions
7277
end
7378
end
7479

75-
def matches_hash_conditions(adapter, subject, conditions)
80+
def matches_hash_conditions?(adapter, subject, conditions)
7681
conditions.all? do |name, value|
7782
if adapter.override_condition_matching?(subject, name, value)
7883
adapter.matches_condition?(subject, name, value)
@@ -97,12 +102,29 @@ def condition_match?(attribute, value)
97102

98103
def hash_condition_match?(attribute, value)
99104
if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
100-
attribute.to_a.any? { |element| matches_conditions_hash?(element, value) }
105+
array_like_matches_condition_hash?(attribute, value)
101106
else
102107
attribute && matches_conditions_hash?(attribute, value)
103108
end
104109
end
105110

111+
def array_like_matches_condition_hash?(attribute, value)
112+
if attribute.any?
113+
attribute.any? { |element| matches_conditions_hash?(element, value) }
114+
else
115+
# you can use `nil`s in your ability definition to tell cancancan to find
116+
# objects that *don't* have any children in a has_many relationship.
117+
#
118+
# for example, given ability:
119+
# => can :read, Article, comments: { id: nil }
120+
# cancancan will return articles where `article.comments == []`
121+
#
122+
# this is implemented here. `attribute` is `article.comments`, and it's an empty array.
123+
# the expression below returns true if this was expected.
124+
!value.values.empty? && value.values.all?(&:nil?)
125+
end
126+
end
127+
106128
def call_block_with_all(action, subject, *extra_args)
107129
if subject.class == Class
108130
@block.call(action, subject, nil, *extra_args)

lib/cancan/config.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ def self.valid_accessible_by_strategies
1111
strategies
1212
end
1313

14+
# You can disable the rules compressor if it's causing unexpected issues.
15+
def self.rules_compressor_enabled
16+
return @rules_compressor_enabled if defined?(@rules_compressor_enabled)
17+
18+
@rules_compressor_enabled = true
19+
end
20+
21+
def self.rules_compressor_enabled=(value)
22+
@rules_compressor_enabled = value
23+
end
24+
25+
def self.with_rules_compressor_enabled(value)
26+
return yield if value == rules_compressor_enabled
27+
28+
begin
29+
rules_compressor_enabled_was = rules_compressor_enabled
30+
@rules_compressor_enabled = value
31+
yield
32+
ensure
33+
@rules_compressor_enabled = rules_compressor_enabled_was
34+
end
35+
end
36+
1437
# Determines how CanCan should build queries when calling accessible_by,
1538
# if the query will contain a join. The default strategy is `:subquery`.
1639
#

0 commit comments

Comments
 (0)