require 'cancan' module Cancando include CanCan::Ability def can_do(actions, resource, conditions = {}) add(abilities, actions, resource, conditions) end def cannot_do(actions, resource, conditions = {}) add(restrictions, actions, resource, conditions) end def abilities @abilities ||= Hash.new { |hash, k| hash[k] = [] } end def restrictions @restrictions ||= Hash.new { |hash, k| hash[k] = [] } end def apply merge_and_grant(abilities, 'can') if abilities merge_and_grant(restrictions, 'cannot') if restrictions end private def add(target, actions, resource, conditions) Array(actions).each do |action| target[resource] << {action: action, conditions: conditions} end end def merge_and_grant(abilities_to_merge, method) abilities_to_merge.each do |resource, abilities| merged_abilities = [] abilities.each do |ability| merge_abilities(merged_abilities, ability) end grant_abilities(merged_abilities, resource, method) end end def merge_abilities(abilities, ability_to_merge) if abilities.empty? abilities << ability_to_merge return end merged = false abilities.each do |ability| if same_actions?(ability[:action], ability_to_merge[:action]) && same_single_condition_attr?(ability_to_merge[:conditions], ability[:conditions]) merge_conditions(ability, ability_to_merge) merge_actions(ability, ability_to_merge) merged = true break elsif equal_conditions?(ability_to_merge[:conditions], ability[:conditions]) merge_actions(ability, ability_to_merge) merged = true break end end abilities << ability_to_merge unless merged end def same_actions?(actions1, actions2) if actions1.is_a? Array actions1.include? actions2 else actions1 == actions2 end end def same_single_condition_attr?(conditions1, conditions2) conditions1.length == 1 && conditions1.keys == conditions2.keys end def equal_conditions?(conditions1, conditions2) conditions1.sort == conditions2.sort end def merge_conditions(ability1, ability2) attr_value1 = ability1[:conditions].first[1] attr_value2 = ability2[:conditions].first[1] attr_name = ability2[:conditions].first[0] merged_value = (Array(attr_value1) + Array(attr_value2)).uniq ability1[:conditions][attr_name] = merged_value.length == 1 ? merged_value[0] : merged_value end def merge_actions(ability1, ability2) ability1[:action] = (Array(ability1[:action]) + Array(ability2[:action])).uniq end # apply abilities by cancanan methods can or cannot def grant_abilities(abilities, resource, method) abilities.each do |ability| send(method, ability[:action], resource, ability[:conditions]) end end end