Unverified Commit 8fc2a2db authored by Alessandro Rodi's avatar Alessandro Rodi Committed by GitHub
Browse files

[WIP] Refactorings (#452)

Split code in different modules
parent 4a182db4
......@@ -10,30 +10,32 @@ Style/FrozenStringLiteralComment:
Style/EmptyMethod:
Enabled: false
Style/VariableNumber:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Metrics/LineLength:
Max: 120
Metrics/ClassLength:
Metrics/BlockLength:
Exclude:
- 'lib/cancan/controller_resource.rb'
- 'lib/cancan/rule.rb'
- 'lib/cancan/matchers.rb'
- '**/*_spec.rb'
Metrics/ModuleLength:
# TODO
# Offense count: 2
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
# NameWhitelist: is_a?
Style/PredicateName:
Exclude:
- "**/*_spec.rb"
- 'spec/**/*'
- 'lib/cancan/ability.rb'
- 'lib/cancan/model_adapters/active_record_adapter.rb'
Metrics/BlockLength:
Exclude:
- 'lib/cancan/matchers.rb'
- '**/*_spec.rb'
# TODO
Lint/AmbiguousBlockAssociation:
Enabled: false
AllCops:
TargetRubyVersion: 2.0
......
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2017-03-26 15:25:15 +0200 using RuboCop version 0.46.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 12
Metrics/AbcSize:
Max: 21
# Offense count: 3
# Configuration parameters: CountComments.
Metrics/BlockLength:
Max: 58
# Offense count: 4
Metrics/CyclomaticComplexity:
Max: 9
# Offense count: 13
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 21
# Offense count: 4
Metrics/PerceivedComplexity:
Max: 10
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
# SupportedStyles: line_count_based, semantic, braces_for_chaining
# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
# FunctionalMethods: let, let!, subject, watch
# IgnoredMethods: lambda, proc, it
Style/BlockDelimiters:
Exclude:
- 'spec/cancan/matchers_spec.rb'
# Offense count: 4
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts.
Style/FileName:
Exclude:
- 'spec/cancan/inherited_resource_spec_BACKUP_24010.rb'
- 'spec/cancan/inherited_resource_spec_BASE_24010.rb'
- 'spec/cancan/inherited_resource_spec_LOCAL_24010.rb'
- 'spec/cancan/inherited_resource_spec_REMOTE_24010.rb'
# Offense count: 2
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
# NameWhitelist: is_a?
Style/PredicateName:
Exclude:
- 'spec/**/*'
- 'lib/cancan/ability.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
Style/SpaceAroundEqualsInParameterDefault:
Exclude:
- 'spec/cancan/inherited_resource_spec_BASE_24010.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
# SupportedStyles: space, no_space, compact
Style/SpaceInsideHashLiteralBraces:
Exclude:
- 'spec/cancan/inherited_resource_spec_BASE_24010.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/TrailingWhitespace:
Exclude:
- 'lib/cancan.rb'
# has a bug
Style/FileName:
Exclude:
- 'Appraisals'
# disagree
Lint/AmbiguousBlockAssociation:
Enabled: false
Unreleased
* ...
* Removed support for dynamic finders (coorasse)
2.1.0 (Nov 10th, 2017)
......
......@@ -2,10 +2,10 @@
source "https://rubygems.org"
gem "activerecord", "~> 4.2.0", :require => "active_record"
gem "activesupport", "~> 4.2.0", :require => "active_support/all"
gem "actionpack", "~> 4.2.0", :require => "action_pack"
gem "nokogiri", "~> 1.6.8", :require => "nokogiri"
gem "activerecord", "~> 4.2.0", require: "active_record"
gem "activesupport", "~> 4.2.0", require: "active_support/all"
gem "actionpack", "~> 4.2.0", require: "action_pack"
gem "nokogiri", "~> 1.6.8", require: "nokogiri"
platforms :jruby do
gem "activerecord-jdbcsqlite3-adapter"
......@@ -17,4 +17,4 @@ platforms :ruby, :mswin, :mingw do
gem "pg"
end
gemspec :path => "../"
gemspec path: "../"
......@@ -2,9 +2,9 @@
source "https://rubygems.org"
gem "activerecord", "~> 5.0.2", :require => "active_record"
gem "activesupport", "~> 5.0.2", :require => "active_support/all"
gem "actionpack", "~> 5.0.2", :require => "action_pack"
gem "activerecord", "~> 5.0.2", require: "active_record"
gem "activesupport", "~> 5.0.2", require: "active_support/all"
gem "actionpack", "~> 5.0.2", require: "action_pack"
platforms :jruby do
gem "activerecord-jdbcsqlite3-adapter"
......@@ -16,4 +16,4 @@ platforms :ruby, :mswin, :mingw do
gem "pg"
end
gemspec :path => "../"
gemspec path: "../"
......@@ -2,9 +2,9 @@
source "https://rubygems.org"
gem "activerecord", "~> 5.1.0", :require => "active_record"
gem "activesupport", "~> 5.1.0", :require => "active_support/all"
gem "actionpack", "~> 5.1.0", :require => "action_pack"
gem "activerecord", "~> 5.1.0", require: "active_record"
gem "activesupport", "~> 5.1.0", require: "active_support/all"
gem "actionpack", "~> 5.1.0", require: "action_pack"
platforms :jruby do
gem "activerecord-jdbcsqlite3-adapter"
......@@ -16,4 +16,4 @@ platforms :ruby, :mswin, :mingw do
gem "pg"
end
gemspec :path => "../"
gemspec path: "../"
......@@ -10,6 +10,6 @@ require 'cancan/model_adapters/abstract_adapter'
require 'cancan/model_adapters/default_adapter'
if defined? ActiveRecord
require 'cancan/model_adapters/active_record_adapter'
require 'cancan/model_adapters/active_record_adapter'
require 'cancan/model_adapters/active_record_4_adapter'
end
require_relative 'ability/rules.rb'
require_relative 'ability/actions.rb'
module CanCan
# This module is designed to be included into an Ability class. This will
# provide the "can" methods for defining and checking abilities.
......@@ -15,6 +17,9 @@ module CanCan
# end
#
module Ability
include CanCan::Ability::Rules
include CanCan::Ability::Actions
# Check if the user has permission to perform a given action on an object.
#
# can? :destroy, @project
......@@ -149,58 +154,12 @@ module CanCan
add_rule(Rule.new(false, action, subject, conditions, block))
end
# Alias one or more actions into another one.
#
# alias_action :update, :destroy, :to => :modify
# can :modify, Comment
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, Comment # => true
# can? :destroy, Comment # => true
#
# This only works in one direction. Passing the aliased action into the "can?" call
# will not work because aliases are meant to generate more generic actions.
#
# alias_action :update, :destroy, :to => :modify
# can :update, Comment
# can? :modify, Comment # => false
#
# Unless that exact alias is used.
#
# can :modify, Comment
# can? :modify, Comment # => true
#
# The following aliases are added by default for conveniently mapping common controller actions.
#
# alias_action :index, :show, :to => :read
# alias_action :new, :to => :create
# alias_action :edit, :to => :update
#
# This way one can use params[:action] in the controller to determine the permission.
def alias_action(*args)
target = args.pop[:to]
validate_target(target)
aliased_actions[target] ||= []
aliased_actions[target] += args
end
# User shouldn't specify targets with names of real actions or it will cause Seg fault
def validate_target(target)
error_message = "You can't specify target (#{target}) as alias because it is real action name"
raise Error, error_message if aliased_actions.values.flatten.include? target
end
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
def aliased_actions
@aliased_actions ||= default_alias_actions
end
# Removes previously aliased actions including the defaults.
def clear_aliased_actions
@aliased_actions = {}
end
def model_adapter(model_class, action)
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
......@@ -262,29 +221,16 @@ module CanCan
# }
def permissions
permissions_list = { can: {}, cannot: {} }
rules.each do |rule|
subjects = rule.subjects
expand_actions(rule.actions).each do |action|
if rule.base_behavior
permissions_list[:can][action] ||= []
permissions_list[:can][action] += subjects.map(&:to_s)
else
permissions_list[:cannot][action] ||= []
permissions_list[:cannot][action] += subjects.map(&:to_s)
end
end
end
rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
permissions_list
end
protected
# Must be protected as an ability can merge with other abilities.
# This means that an ability must expose their rules with another ability.
def rules
@rules ||= []
def extract_rule_in_permissions(permissions_list, rule)
expand_actions(rule.actions).each do |action|
container = rule.base_behavior ? :can : :cannot
permissions_list[container][action] ||= []
permissions_list[container][action] += rule.subjects.map(&:to_s)
end
end
private
......@@ -297,26 +243,6 @@ module CanCan
end
end
# Accepts an array of actions and returns an array of actions which match.
# This should be called before "matches?" and other checking methods since they
# rely on the actions to be expanded.
def expand_actions(actions)
expanded_actions[actions] ||= begin
expanded = []
actions.each do |action|
expanded << action
if (aliases = aliased_actions[action])
expanded += expand_actions(aliases)
end
end
expanded
end
end
def expanded_actions
@expanded_actions ||= {}
end
# It translates to an array the subject or the hash with multiple subjects given to can?.
def extract_subjects(subject)
if subject.is_a?(Hash) && subject.key?(:any)
......@@ -326,97 +252,9 @@ module CanCan
end
end
# Given an action, it will try to find all of the actions which are aliased to it.
# This does the opposite kind of lookup as expand_actions.
def aliases_for_action(action)
results = [action]
aliased_actions.each do |aliased_action, actions|
results += aliases_for_action(aliased_action) if actions.include? action
end
results
end
def add_rule(rule)
rules << rule
add_rule_to_index(rule, rules.size - 1)
end
def add_rule_to_index(rule, position)
@rules_index ||= Hash.new { |h, k| h[k] = [] }
subjects = rule.subjects.compact
subjects << :all if subjects.empty?
subjects.each do |subject|
@rules_index[subject] << position
end
end
def alternative_subjects(subject)
subject = subject.class unless subject.is_a?(Module)
[:all, *subject.ancestors, subject.class.to_s]
end
# Returns an array of Rule instances which match the action and subject
# This does not take into consideration any hash conditions or block statements
def relevant_rules(action, subject)
return [] unless @rules
relevant = possible_relevant_rules(subject).select do |rule|
rule.expanded_actions = expand_actions(rule.actions)
rule.relevant? action, subject
end
relevant.reverse!.uniq!
optimize_order! relevant
relevant
end
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
def optimize_order!(rules)
first_can_in_group = -1
rules.each_with_index do |rule, i|
(first_can_in_group = -1) && next unless rule.base_behavior
(first_can_in_group = i) && next if first_can_in_group == -1
next unless rule.subjects == [:all]
rules[i] = rules[first_can_in_group]
rules[first_can_in_group] = rule
first_can_in_group += 1
end
end
def possible_relevant_rules(subject)
if subject.is_a?(Hash)
rules
else
positions = @rules_index.values_at(subject, *alternative_subjects(subject))
positions.flatten!.sort!
positions.map { |i| @rules[i] }
end
end
def relevant_rules_for_match(action, subject)
relevant_rules(action, subject).each do |rule|
next unless rule.only_raw_sql?
raise Error,
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
" The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
def relevant_rules_for_query(action, subject)
relevant_rules(action, subject).each do |rule|
if rule.only_block?
raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
" The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
end
def default_alias_actions
{
read: %i[index show],
create: [:new],
update: [:edit]
}
[:all, *subject.ancestors, subject.class.to_s]
end
end
end
module CanCan
module Ability
module Actions
# Alias one or more actions into another one.
#
# alias_action :update, :destroy, :to => :modify
# can :modify, Comment
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, Comment # => true
# can? :destroy, Comment # => true
#
# This only works in one direction. Passing the aliased action into the "can?" call
# will not work because aliases are meant to generate more generic actions.
#
# alias_action :update, :destroy, :to => :modify
# can :update, Comment
# can? :modify, Comment # => false
#
# Unless that exact alias is used.
#
# can :modify, Comment
# can? :modify, Comment # => true
#
# The following aliases are added by default for conveniently mapping common controller actions.
#
# alias_action :index, :show, :to => :read
# alias_action :new, :to => :create
# alias_action :edit, :to => :update
#
# This way one can use params[:action] in the controller to determine the permission.
def alias_action(*args)
target = args.pop[:to]
validate_target(target)
aliased_actions[target] ||= []
aliased_actions[target] += args
end
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
def aliased_actions
@aliased_actions ||= default_alias_actions
end
# Removes previously aliased actions including the defaults.
def clear_aliased_actions
@aliased_actions = {}
end
private
def default_alias_actions
{
read: %i[index show],
create: [:new],
update: [:edit]
}
end
# Given an action, it will try to find all of the actions which are aliased to it.
# This does the opposite kind of lookup as expand_actions.
def aliases_for_action(action)
results = [action]
aliased_actions.each do |aliased_action, actions|
results += aliases_for_action(aliased_action) if actions.include? action
end
results
end
def expanded_actions
@expanded_actions ||= {}
end
# Accepts an array of actions and returns an array of actions which match.
# This should be called before "matches?" and other checking methods since they
# rely on the actions to be expanded.
def expand_actions(actions)
expanded_actions[actions] ||= begin
expanded = []
actions.each do |action|
expanded << action
if (aliases = aliased_actions[action])
expanded += expand_actions(aliases)
end
end
expanded
end
end
end
end
end
module CanCan
module Ability
module Rules
protected
# Must be protected as an ability can merge with other abilities.
# This means that an ability must expose their rules with another ability.
def rules
@rules ||= []
end
private
def add_rule(rule)
rules << rule
add_rule_to_index(rule, rules.size - 1)
end
def add_rule_to_index(rule, position)
@rules_index ||= Hash.new { |h, k| h[k] = [] }
subjects = rule.subjects.compact
subjects << :all if subjects.empty?
subjects.each do |subject|
@rules_index[subject] << position
end
end
# Returns an array of Rule instances which match the action and subject
# This does not take into consideration any hash conditions or block statements
def relevant_rules(action, subject)
return [] unless @rules
relevant = possible_relevant_rules(subject).select do |rule|
rule.expanded_actions = expand_actions(rule.actions)
rule.relevant? action, subject
end
relevant.reverse!.uniq!
optimize_order! relevant
relevant
end
def possible_relevant_rules(subject)
if subject.is_a?(Hash)
rules
else
positions = @rules_index.values_at(subject, *alternative_subjects(subject))
positions.flatten!.sort!
positions.map { |i| @rules[i] }
end
end
def relevant_rules_for_match(action, subject)
relevant_rules(action, subject).each do |rule|
next unless rule.only_raw_sql?
raise Error,
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
" The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
def relevant_rules_for_query(action, subject)
relevant_rules(action, subject).each do |rule|
if rule.only_block?
raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
" The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
end
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
def optimize_order!(rules)
first_can_in_group = -1
rules.each_with_index do |rule, i|
(first_can_in_group = -1) && next unless rule.base_behavior
(first_can_in_group = i) && next if first_can_in_group == -1
next unless rule.subjects == [:all]
rules[i] = rules[first_can_in_group]
rules[first_can_in_group] = rule
first_can_in_group += 1