Skip to content

Commit

Permalink
Fix breaking change introduced in #204 (#614)
Browse files Browse the repository at this point in the history
* Fix breaking change introduced in #204

* Fix

Co-authored-by: Fumiaki MATSUSHIMA <mtsmfm@gmail.com>
  • Loading branch information
coorasse and mtsmfm committed Mar 6, 2020
1 parent 48f1fad commit e2994f6
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased

* [#571](https://github.com/CanCanCommunity/cancancan/pull/571): Allows to check ability even the object implements `#to_a`. ([@mtsmfm][])
* [#612](https://github.com/CanCanCommunity/cancancan/pull/612): Suppress keyword arguments warning for Ruby 2.7.0. ([@koic][])

## 3.0.2
Expand Down Expand Up @@ -654,4 +655,5 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co
[@kaspernj]: https://github.com/kaspernj
[@frostblooded]: https://github.com/frostblooded
[@eloyesp]: https://github.com/eloyesp
[@mtsmfm]: https://github.com/mtsmfm
[@koic]: https://github.com/koic
29 changes: 29 additions & 0 deletions lib/cancan/relevant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module CanCan
module Relevant
# Matches both the action, subject, and attribute, not necessarily the conditions
def relevant?(action, subject)
subject = subject.values.first if subject.class == Hash
@match_all || (matches_action?(action) && matches_subject?(subject))
end

private

def matches_action?(action)
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
end

def matches_subject?(subject)
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
end

def matches_subject_class?(subject)
@subjects.any? do |sub|
sub.is_a?(Module) && (subject.is_a?(sub) ||
subject.class.to_s == sub.to_s ||
(subject.is_a?(Module) && subject.ancestors.include?(sub)))
end
end
end
end
41 changes: 16 additions & 25 deletions lib/cancan/rule.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# frozen_string_literal: true

require_relative 'conditions_matcher.rb'
require_relative 'relevant.rb'

module CanCan
# This class is used internally and should only be called through Ability.
# it holds the information about a "can" call made on Ability and provides
# helpful methods to determine permission checking and conditions hash generation.
class Rule # :nodoc:
include ConditionsMatcher
include Relevant
include ParameterValidators
attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
attr_writer :expanded_actions, :conditions
Expand All @@ -24,9 +27,9 @@ def initialize(base_behavior, action, subject, *extra_args, &block)
raise Error, "Subject is required for #{action}" if action && subject.nil?

@base_behavior = base_behavior
@actions = Array(action)
@subjects = Array(subject)
@attributes = Array(attributes)
@actions = wrap(action)
@subjects = wrap(subject)
@attributes = wrap(attributes)
@conditions = extra_args || {}
@block = block
end
Expand Down Expand Up @@ -57,12 +60,6 @@ def catch_all?
(!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
end

# Matches both the action, subject, and attribute, not necessarily the conditions
def relevant?(action, subject)
subject = subject.values.first if subject.class == Hash
@match_all || (matches_action?(action) && matches_subject?(subject))
end

def only_block?
conditions_empty? && @block
end
Expand Down Expand Up @@ -104,22 +101,6 @@ def matches_attributes?(attribute)

private

def matches_action?(action)
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
end

def matches_subject?(subject)
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
end

def matches_subject_class?(subject)
@subjects.any? do |sub|
sub.is_a?(Module) && (subject.is_a?(sub) ||
subject.class.to_s == sub.to_s ||
(subject.is_a?(Module) && subject.ancestors.include?(sub)))
end
end

def parse_attributes_from_extra_args(args)
attributes = args.shift if valid_attribute_param?(args.first)
extra_args = args.shift
Expand All @@ -132,5 +113,15 @@ def condition_and_block_check(conditions, block, action, subject)
raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\
"Check \":#{action} #{subject}\" ability."
end

def wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
end
25 changes: 25 additions & 0 deletions spec/cancan/ability_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,31 @@ class Container < Hash
expect(@ability.attributes_for(:new, Range)).to eq(foo: 'foo', bar: 123, baz: 'baz')
end

it 'allows to check ability even the object implements `#to_a`' do
stub_const('X', Class.new do
def self.to_a
[]
end
end)

@ability.can :read, X
expect(@ability.can?(:read, X.new)).to be(true)
end

it 'respects `#to_ary`' do
stub_const('X', Class.new do
def self.to_ary
[Y]
end
end)

stub_const('Y', Class.new)

@ability.can :read, X
expect(@ability.can?(:read, X.new)).to be(false)
expect(@ability.can?(:read, Y.new)).to be(true)
end

# rubocop:disable Style/SymbolProc
describe 'different usages of blocks and procs' do
class A
Expand Down

0 comments on commit e2994f6

Please sign in to comment.