14 Commits
1.1 ... 1.2.0

Author SHA1 Message Date
Ryan Bates
1b4377cbf3 releasing version 1.2.0 2010-07-19 09:21:14 -07:00
Ryan Bates
1ade44221a load parent resources for collection actions such 'index' 2010-05-21 15:22:21 -07:00
Ryan Bates
2a3dd85a18 adding :name option to load_and_authorize_resource if it does not match controller - closes #65 2010-05-21 14:20:45 -07:00
Ryan Bates
dfd84a10ed improving inline documentation 2010-05-21 13:41:24 -07:00
John Allison
7543eedd6a fixing issue when using accessible_by with nil can conditions - closes #66 2010-05-20 17:06:10 -07:00
Logan Raarup
605063b974 Make sure conditions on associations are pluralized 2010-05-21 07:31:29 +08:00
Ryan Bates
06296b0a40 support has_many association or arrays in can conditions hash 2010-04-22 17:39:22 -07:00
Ryan Bates
e20081454f adding joins clause to accessible_by when conditions are across associations 2010-04-20 17:02:28 -07:00
Ryan Bates
4da31c0709 can has cheezburger? (thanks Seivan) 2010-04-20 14:24:26 -07:00
Ryan Bates
5aa6252df6 removing unused methods and a bit more refactoring 2010-04-18 00:44:42 -07:00
Ryan Bates
bbbc8a68e0 refactoring much of Ability class into separate CanDefinition class 2010-04-18 00:11:15 -07:00
Ryan Bates
232ecd5b4b releasing 1.1.1 which fixes behavior in Rails 3 by properly initializing ResourceAuthorization 2010-04-17 14:01:20 -07:00
Ryan Bates
e1652ea424 adding admin namespace wiki page link to readme 2010-04-17 13:27:01 -07:00
Ryan Bates
b9995c6147 minor changes to readme 2010-04-17 12:37:32 -07:00
19 changed files with 507 additions and 317 deletions

View File

@@ -1,3 +1,23 @@
1.2.0 (July 16, 2010)
* Load nested parent resources on collection actions such as "index" (thanks dohzya)
* Adding :name option to load_and_authorize_resource if it does not match controller - see issue #65
* Fixing issue when using accessible_by with nil can conditions (thanks jrallison) - see issue #66
* Pluralize table name for belongs_to associations in can conditions hash (thanks logandk) - see issue #62
* Support has_many association or arrays in can conditions hash
* Adding joins clause to accessible_by when conditions are across associations
1.1.1 (April 17, 2010)
* Fixing behavior in Rails 3 by properly initializing ResourceAuthorization
1.1.0 (April 17, 2010) 1.1.0 (April 17, 2010)
* Supporting arrays, ranges, and nested hashes in ability conditions * Supporting arrays, ranges, and nested hashes in ability conditions

View File

@@ -4,7 +4,7 @@ Wiki[http://wiki.github.com/ryanb/cancan] | RDocs[http://rdoc.info/projects/ryan
CanCan is an authorization solution for Ruby on Rails. This restricts what a given user is allowed to access throughout the application. It is completely decoupled from any role based implementation and focusses on keeping permission logic in a single location (the +Ability+ class) so it is not duplicated across controllers, views, and database queries. CanCan is an authorization solution for Ruby on Rails. This restricts what a given user is allowed to access throughout the application. It is completely decoupled from any role based implementation and focusses on keeping permission logic in a single location (the +Ability+ class) so it is not duplicated across controllers, views, and database queries.
This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). This will provide a +current_user+ method which CanCan relies on. See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior. This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]) that provides a +current_user+ method which CanCan relies on. See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
== Installation == Installation
@@ -51,7 +51,7 @@ The "authorize!" method in the controller will raise an exception if the user is
authorize! :read, @article authorize! :read, @article
end end
Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it for each action. Setting this for every action can be tedious, therefore the +load_and_authorize_resource+ method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for each action.
class ArticlesController < ApplicationController class ArticlesController < ApplicationController
load_and_authorize_resource load_and_authorize_resource
@@ -63,7 +63,7 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_
See {Authorizing Controller Actions}[http://wiki.github.com/ryanb/cancan/authorizing-controller-actions] for more information See {Authorizing Controller Actions}[http://wiki.github.com/ryanb/cancan/authorizing-controller-actions] for more information
If the user authorization fails a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+. If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception| rescue_from CanCan::AccessDenied do |exception|
@@ -110,7 +110,7 @@ If the block returns true then the user has that :+update+ ability for that proj
== Aliasing Actions == Aliasing Actions
You will usually be working with four actions when defining and checking permissions: :+read+, :+create+, :+update+, :+destroy+. These aren't the same as the 7 RESTful actions in Rails. CanCan adds some default aliases for mapping those actions. You will usually be working with four actions when defining and checking permissions: :+read+, :+create+, :+update+, :+destroy+. These aren't the same as the 7 RESTful actions in Rails. CanCan automatically adds some default aliases for mapping those actions.
alias_action :index, :show, :to => :read alias_action :index, :show, :to => :read
alias_action :new, :to => :create alias_action :new, :to => :create
@@ -139,6 +139,7 @@ See {Fetching Records}[http://wiki.github.com/ryanb/cancan/fetching-records] for
* {Upgrading to 1.1}[http://wiki.github.com/ryanb/cancan/upgrading-to-11] * {Upgrading to 1.1}[http://wiki.github.com/ryanb/cancan/upgrading-to-11]
* {Testing Abilities}[http://wiki.github.com/ryanb/cancan/testing-abilities] * {Testing Abilities}[http://wiki.github.com/ryanb/cancan/testing-abilities]
* {Accessing Request Data}[http://wiki.github.com/ryanb/cancan/accessing-request-data] * {Accessing Request Data}[http://wiki.github.com/ryanb/cancan/accessing-request-data]
* {Admin Namespace}[http://wiki.github.com/ryanb/cancan/admin-namespace]
* {See more}[http://wiki.github.com/ryanb/cancan/] * {See more}[http://wiki.github.com/ryanb/cancan/]
== Special Thanks == Special Thanks

View File

@@ -1,6 +1,6 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "cancan" s.name = "cancan"
s.version = "1.1.0" s.version = "1.2.0"
s.author = "Ryan Bates" s.author = "Ryan Bates"
s.email = "ryan@railscasts.com" s.email = "ryan@railscasts.com"
s.homepage = "http://github.com/ryanb/cancan" s.homepage = "http://github.com/ryanb/cancan"

View File

@@ -1,4 +1,5 @@
require 'cancan/ability' require 'cancan/ability'
require 'cancan/can_definition'
require 'cancan/controller_resource' require 'cancan/controller_resource'
require 'cancan/resource_authorization' require 'cancan/resource_authorization'
require 'cancan/controller_additions' require 'cancan/controller_additions'

View File

@@ -16,7 +16,7 @@ module CanCan
# end # end
# #
module Ability module Ability
# Use to check the user's permission for a given action and object. # Use to check if the user has permission to perform a given action on an object.
# #
# can? :destroy, @project # can? :destroy, @project
# #
@@ -47,12 +47,11 @@ module CanCan
# assert ability.cannot?(:destroy, Project.new) # assert ability.cannot?(:destroy, Project.new)
# end # end
# #
# Also see the RSpec Matchers to aid in testing.
def can?(action, subject, *extra_args) def can?(action, subject, *extra_args)
matching_can_definition(action, subject) do |base_behavior, defined_actions, defined_subjects, defined_conditions, defined_block| raise Error, "Nom nom nom. I eated it." if action == :has && subject == :cheezburger
result = can_perform_action?(action, subject, defined_actions, defined_subjects, defined_conditions, defined_block, extra_args) can_definition = matching_can_definition(action, subject)
return base_behavior ? result : !result can_definition && can_definition.can?(action, subject, extra_args)
end
false
end end
# Convenience method which works the same as "can?" but returns the opposite value. # Convenience method which works the same as "can?" but returns the opposite value.
@@ -78,8 +77,8 @@ module CanCan
# #
# can :read, Project, :active => true, :user_id => user.id # can :read, Project, :active => true, :user_id => user.id
# #
# Here the user can only see active projects which he owns. See ControllerAdditions#conditions for a way to # Here the user can only see active projects which he owns. See ActiveRecordAdditions#accessible_by
# use this in database queries. # for how to use this in database queries.
# #
# If the conditions hash does not give you enough control over defining abilities, you can use a block to # If the conditions hash does not give you enough control over defining abilities, you can use a block to
# write any Ruby code you want. # write any Ruby code you want.
@@ -117,11 +116,10 @@ module CanCan
# can? :read, :stats # => true # can? :read, :stats # => true
# #
def can(action, subject, conditions = nil, &block) def can(action, subject, conditions = nil, &block)
@can_definitions ||= [] can_definitions << CanDefinition.new(true, action, subject, conditions, block)
@can_definitions << [true, action, subject, conditions, block]
end end
# Define an ability which cannot be done. Accepts the same arguments as "can". # Defines an ability which cannot be done. Accepts the same arguments as "can".
# #
# can :read, :all # can :read, :all
# cannot :read, Comment # cannot :read, Comment
@@ -134,8 +132,7 @@ module CanCan
# end # end
# #
def cannot(action, subject, conditions = nil, &block) def cannot(action, subject, conditions = nil, &block)
@can_definitions ||= [] can_definitions << CanDefinition.new(false, action, subject, conditions, block)
@can_definitions << [false, action, subject, conditions, block]
end end
# Alias one or more actions into another one. # Alias one or more actions into another one.
@@ -194,23 +191,36 @@ module CanCan
# If the ability is not defined then false is returned so be sure to take that into consideration. # If the ability is not defined then false is returned so be sure to take that into consideration.
# If the ability is defined using a block then this will raise an exception since a hash of conditions cannot be # If the ability is defined using a block then this will raise an exception since a hash of conditions cannot be
# determined from that. # determined from that.
def conditions(action, subject) def conditions(action, subject, options = {})
matching_can_definition(action, subject) do |base_behavior, defined_actions, defined_subjects, defined_conditions, defined_block| can_definition = matching_can_definition(action, subject)
raise Error, "Cannot determine ability conditions from block for #{action.inspect} #{subject.inspect}" if defined_block if can_definition
return defined_conditions || {} raise Error, "Cannot determine ability conditions from block for #{action.inspect} #{subject.inspect}" if can_definition.block
can_definition.conditions(options) || {}
else
false
end
end
# Returns the associations used in conditions. This is usually used in the :joins option for a search.
# See ActiveRecordAdditions#accessible_by for use in Active Record.
def association_joins(action, subject)
can_definition = matching_can_definition(action, subject)
if can_definition
raise Error, "Cannot determine association joins from block for #{action.inspect} #{subject.inspect}" if can_definition.block
can_definition.association_joins
end end
false
end end
private private
def matching_can_definition(action, subject, &block) def can_definitions
(@can_definitions || []).reverse.each do |base_behavior, defined_action, defined_subject, defined_conditions, defined_block| @can_definitions ||= []
defined_actions = expand_actions(defined_action) end
defined_subjects = [defined_subject].flatten
if includes_action?(defined_actions, action) && includes_subject?(defined_subjects, subject) def matching_can_definition(action, subject)
return block.call(base_behavior, defined_actions, defined_subjects, defined_conditions, defined_block) can_definitions.reverse.detect do |can_definition|
end can_definition.expand_actions(aliased_actions)
can_definition.matches? action, subject
end end
end end
@@ -221,55 +231,5 @@ module CanCan
:update => [:edit], :update => [:edit],
} }
end end
def expand_actions(actions)
[actions].flatten.map do |action|
if aliased_actions[action]
[action, *aliased_actions[action]]
else
action
end
end.flatten
end
def can_perform_action?(action, subject, defined_actions, defined_subjects, defined_conditions, defined_block, extra_args)
if defined_block
block_args = []
block_args << action if defined_actions.include?(:manage)
block_args << (subject.class == Class ? subject : subject.class) if defined_subjects.include?(:all)
block_args << (subject.class == Class ? nil : subject)
block_args += extra_args
defined_block.call(*block_args)
elsif defined_conditions
if subject.class == Class
true
else
matches_conditions? subject, defined_conditions
end
else
true
end
end
def matches_conditions?(subject, defined_conditions)
defined_conditions.all? do |name, value|
attribute = subject.send(name)
if value.kind_of?(Hash)
matches_conditions? attribute, value
elsif value.kind_of?(Array) || value.kind_of?(Range)
value.include? attribute
else
attribute == value
end
end
end
def includes_action?(actions, action)
actions.include?(:manage) || actions.include?(action)
end
def includes_subject?(subjects, subject)
subjects.include?(:all) || subjects.include?(subject) || subjects.any? { |c| c.kind_of?(Class) && subject.kind_of?(c) }
end
end end
end end

View File

@@ -1,5 +1,5 @@
module CanCan module CanCan
# This module is automatically included into all Active Record. # This module is automatically included into all Active Record models.
module ActiveRecordAdditions module ActiveRecordAdditions
module ClassMethods module ClassMethods
# Returns a scope which fetches only the records that the passed ability # Returns a scope which fetches only the records that the passed ability
@@ -20,11 +20,12 @@ module CanCan
# Here only the articles which the user can update are returned. This # Here only the articles which the user can update are returned. This
# internally uses Ability#conditions method, see that for more information. # internally uses Ability#conditions method, see that for more information.
def accessible_by(ability, action = :read) def accessible_by(ability, action = :read)
conditions = ability.conditions(action, self) || {:id => nil} conditions = ability.conditions(action, self, :tableize => true) || {:id => nil}
joins = ability.association_joins(action, self)
if respond_to? :where if respond_to? :where
where(conditions) where(conditions).joins(joins)
else else
scoped(:conditions => conditions) scoped(:conditions => conditions, :joins => joins)
end end
end end
end end

View File

@@ -0,0 +1,114 @@
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 CanDefinition # :nodoc:
include ActiveSupport::Inflector
attr_reader :block
# The first argument when initializing is the base_behavior which is a true/false
# value. True for "can" and false for "cannot". The next two arguments are the action
# and subject respectively (such as :read, @project). The third argument is a hash
# of conditions and the last one is the block passed to the "can" call.
def initialize(base_behavior, action, subject, conditions, block)
@base_behavior = base_behavior
@actions = [action].flatten
@subjects = [subject].flatten
@conditions = conditions || {}
@block = block
end
# Accepts a hash of aliased 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(aliased_actions)
@expanded_actions = @actions.map do |action|
aliased_actions[action] ? [action, *aliased_actions[action]] : action
end.flatten
end
def matches?(action, subject)
matches_action?(action) && matches_subject?(subject)
end
def can?(action, subject, extra_args)
result = can_without_base_behavior?(action, subject, extra_args)
@base_behavior ? result : !result
end
# Returns a hash of conditions. If the ":tableize => true" option is passed
# it will pluralize the association conditions to match the table name.
def conditions(options = {})
if options[:tableize] && @conditions.kind_of?(Hash)
@conditions.inject({}) do |tableized_conditions, (name, value)|
name = tableize(name).to_sym if value.kind_of? Hash
tableized_conditions[name] = value
tableized_conditions
end
else
@conditions
end
end
def association_joins(conditions = @conditions)
joins = []
conditions.each do |name, value|
if value.kind_of? Hash
nested = association_joins(value)
if nested
joins << {name => nested}
else
joins << name
end
end
end
joins unless joins.empty?
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) || @subjects.any? { |sub| sub.kind_of?(Class) && subject.kind_of?(sub) }
end
def can_without_base_behavior?(action, subject, extra_args)
if @block
call_block(action, subject, extra_args)
elsif @conditions && subject.class != Class
matches_conditions? subject
else
true
end
end
def matches_conditions?(subject, conditions = @conditions)
conditions.all? do |name, value|
attribute = subject.send(name)
if value.kind_of?(Hash)
if attribute.kind_of? Array
attribute.any? { |element| matches_conditions? element, value }
else
matches_conditions? attribute, value
end
elsif value.kind_of?(Array) || value.kind_of?(Range)
value.include? attribute
else
attribute == value
end
end
end
def call_block(action, subject, extra_args)
block_args = []
block_args << action if @expanded_actions.include?(:manage)
block_args << (subject.class == Class ? subject : subject.class) if @subjects.include?(:all)
block_args << (subject.class == Class ? nil : subject)
block_args += extra_args
@block.call(*block_args)
end
end
end

View File

@@ -59,6 +59,11 @@ module CanCan
# #
# load_resource :nested => [:publisher, :author] # load_resource :nested => [:publisher, :author]
# #
# [:+name+]
# The name of the resource if it cannot be determined from controller (string or symbol).
#
# load_resource :name => :article
#
# [:+resource+] # [:+resource+]
# The class to use for the model (string or constant). # The class to use for the model (string or constant).
# #
@@ -102,6 +107,11 @@ module CanCan
# [:+except+] # [:+except+]
# Does not apply before filter to given actions. # Does not apply before filter to given actions.
# #
# [:+name+]
# The name of the resource if it cannot be determined from controller (string or symbol).
#
# load_resource :name => :article
#
# [:+resource+] # [:+resource+]
# The class to use for the model (string or constant). Alternatively pass a symbol # The class to use for the model (string or constant). Alternatively pass a symbol
# to represent a resource which does not have a class. # to represent a resource which does not have a class.

View File

@@ -1,4 +1,7 @@
module CanCan module CanCan
# Used internally to load and authorize a given controller resource.
# This manages finding or building an instance of the resource. If a
# parent is given it will go through the association.
class ControllerResource # :nodoc: class ControllerResource # :nodoc:
def initialize(controller, name, parent = nil, options = {}) def initialize(controller, name, parent = nil, options = {})
raise ImplementationRemoved, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class raise ImplementationRemoved, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
@@ -8,13 +11,17 @@ module CanCan
@options = options @options = options
end end
# Returns the class used for this resource. This can be overriden by the :resource option.
# Sometimes one will use a symbol as the resource if a class does not exist for it. In that
# case "find" and "build" should not be called on it.
def model_class def model_class
if @options[:resource].nil? resource_class = @options[:resource]
if resource_class.nil?
@name.to_s.camelize.constantize @name.to_s.camelize.constantize
elsif @options[:resource].kind_of? String elsif resource_class.kind_of? String
@options[:resource].constantize resource_class.constantize
else else
@options[:resource] resource_class # could be a symbol
end end
end end
@@ -22,12 +29,10 @@ module CanCan
self.model_instance ||= base.find(id) self.model_instance ||= base.find(id)
end end
# Build a new instance of this resource. If it is a class we just call "new" otherwise
# it's an associaiton and "build" is used.
def build(attributes) def build(attributes)
if base.kind_of? Class self.model_instance ||= (base.kind_of?(Class) ? base.new(attributes) : base.build(attributes))
self.model_instance ||= base.new(attributes)
else
self.model_instance ||= base.build(attributes)
end
end end
def model_instance def model_instance
@@ -40,6 +45,8 @@ module CanCan
private private
# The object that methods (such as "find", "new" or "build") are called on.
# If there is a parent it will be the association, otherwise it will be the model's class.
def base def base
@parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class @parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class
end end

View File

@@ -18,7 +18,7 @@ module CanCan
# exception.action # => :read # exception.action # => :read
# exception.subject # => Article # exception.subject # => Article
# #
# If the message is not specified (or is nil) it will default to "You are anot authorized # If the message is not specified (or is nil) it will default to "You are not authorized
# to access this page." This default can be overridden by setting default_message. # to access this page." This default can be overridden by setting default_message.
# #
# exception.default_message = "Default error message" # exception.default_message = "Default error message"

View File

@@ -1,10 +1,10 @@
module CanCan module CanCan
# Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
# This class is used internally, so you do not need to call methods directly on it.
class ResourceAuthorization # :nodoc: class ResourceAuthorization # :nodoc:
attr_reader :params
def self.add_before_filter(controller_class, method, options = {}) def self.add_before_filter(controller_class, method, options = {})
controller_class.before_filter(options.slice(:only, :except)) do |controller| controller_class.before_filter(options.slice(:only, :except)) do |controller|
new(controller, controller.params, options.except(:only, :except)).send(method) ResourceAuthorization.new(controller, controller.params, options.except(:only, :except)).send(method)
end end
end end
@@ -20,17 +20,19 @@ module CanCan
end end
def load_resource def load_resource
unless collection_actions.include? params[:action].to_sym if collection_actions.include? @params[:action].to_sym
if new_actions.include? params[:action].to_sym parent_resource
resource.build(params[model_name.to_sym]) else
elsif params[:id] if new_actions.include? @params[:action].to_sym
resource.find(params[:id]) resource.build(@params[model_name.to_sym])
elsif @params[:id]
resource.find(@params[:id])
end end
end end
end end
def authorize_resource def authorize_resource
@controller.authorize!(params[:action].to_sym, resource.model_instance || resource.model_class) @controller.authorize!(@params[:action].to_sym, resource.model_instance || resource.model_class)
end end
private private
@@ -54,7 +56,7 @@ module CanCan
end end
def model_name def model_name
params[:controller].sub("Controller", "").underscore.split('/').last.singularize @options[:name] || @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
end end
def collection_actions def collection_actions

View File

@@ -168,6 +168,12 @@ describe CanCan::Ability do
@ability.can?(:read, ["test1", "foo"]).should be_true @ability.can?(:read, ["test1", "foo"]).should be_true
end end
it "should allow nested hash of arrays and match any element" do
@ability.can :read, Array, :first => { :to_i => 3 }
@ability.can?(:read, [[1, 2, 3]]).should be_true
@ability.can?(:read, [[4, 5, 6]]).should be_false
end
it "should return conditions for a given ability" do it "should return conditions for a given ability" do
@ability.can :read, Array, :first => 1, :last => 3 @ability.can :read, Array, :first => 1, :last => 3
@ability.conditions(:show, Array).should == {:first => 1, :last => 3} @ability.conditions(:show, Array).should == {:first => 1, :last => 3}
@@ -188,4 +194,10 @@ describe CanCan::Ability do
it "should return false when performed on an action which isn't defined" do it "should return false when performed on an action which isn't defined" do
@ability.conditions(:foo, Array).should == false @ability.conditions(:foo, Array).should == false
end end
it "should has eated cheezburger" do
lambda {
@ability.can? :has, :cheezburger
}.should raise_exception(CanCan::Error, "Nom nom nom. I eated it.")
end
end end

View File

@@ -10,19 +10,19 @@ describe CanCan::ActiveRecordAdditions do
end end
it "should call where(:id => nil) when no ability is defined so no records are found" do it "should call where(:id => nil) when no ability is defined so no records are found" do
stub(@model_class).where(:id => nil) { :no_where } stub(@model_class).where(:id => nil).stub!.joins(nil) { :no_where }
@model_class.accessible_by(@ability, :read).should == :no_where @model_class.accessible_by(@ability, :read).should == :no_where
end end
it "should call where with matching ability conditions" do it "should call where with matching ability conditions" do
@ability.can :read, @model_class, :foo => 1 @ability.can :read, @model_class, :foo => {:bar => 1}
stub(@model_class).where(:foo => 1) { :found_records } stub(@model_class).where(:foos => { :bar => 1 }).stub!.joins([:foo]) { :found_records }
@model_class.accessible_by(@ability, :read).should == :found_records @model_class.accessible_by(@ability, :read).should == :found_records
end end
it "should default to :read ability and use scoped when where isn't available" do it "should default to :read ability and use scoped when where isn't available" do
@ability.can :read, @model_class, :foo => 1 @ability.can :read, @model_class, :foo => {:bar => 1}
stub(@model_class).scoped(:conditions => {:foo => 1}) { :found_records } stub(@model_class).scoped(:conditions => {:foos => {:bar => 1}}, :joins => [:foo]) { :found_records }
@model_class.accessible_by(@ability).should == :found_records @model_class.accessible_by(@ability).should == :found_records
end end
end end

View File

@@ -0,0 +1,44 @@
require "spec_helper"
describe CanCan::CanDefinition do
before(:each) do
@conditions = {}
@can = CanCan::CanDefinition.new(true, :read, Integer, @conditions, nil)
end
it "should return no association joins if none exist" do
@can.association_joins.should be_nil
end
it "should return no association for joins if just attributes" do
@conditions[:foo] = :bar
@can.association_joins.should be_nil
end
it "should return single association for joins" do
@conditions[:foo] = {:bar => 1}
@can.association_joins.should == [:foo]
end
it "should return multiple associations for joins" do
@conditions[:foo] = {:bar => 1}
@conditions[:test] = {1 => 2}
@can.association_joins.map(&:to_s).sort.should == [:foo, :test].map(&:to_s).sort
end
it "should return nested associations for joins" do
@conditions[:foo] = {:bar => {1 => 2}}
@can.association_joins.should == [{:foo => [:bar]}]
end
it "should return table names in conditions for association joins" do
@conditions[:foo] = {:bar => 1}
@conditions[:test] = 1
@can.conditions(:tableize => true).should == { :foos => { :bar => 1}, :test => 1 }
end
it "should return no association joins if conditions is nil" do
can = CanCan::CanDefinition.new(true, :read, Integer, nil, nil)
can.association_joins.should be_nil
end
end

View File

@@ -86,12 +86,23 @@ describe CanCan::ResourceAuthorization do
end end
it "should load nested resource and fetch other resource through the association" do it "should load nested resource and fetch other resource through the association" do
stub(Person).find(456).stub!.abilities.stub!.find(123) { :some_ability } person = Object.new
stub(Person).find(456) { person }
stub(person).abilities.stub!.find(123) { :some_ability }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123, :person_id => 456}, {:nested => :person}) authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123, :person_id => 456}, {:nested => :person})
authorization.load_resource authorization.load_resource
@controller.instance_variable_get(:@person).should == person
@controller.instance_variable_get(:@ability).should == :some_ability @controller.instance_variable_get(:@ability).should == :some_ability
end end
it "should load nested resource for collection action" do
person = Object.new
stub(Person).find(456) { person }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "index", :person_id => 456}, {:nested => :person})
authorization.load_resource
@controller.instance_variable_get(:@person).should == person
end
it "should load nested resource and build resource through a deep association" do it "should load nested resource and build resource through a deep association" do
stub(Person).find(456).stub!.behaviors.stub!.find(789).stub!.abilities.stub!.build(nil) { :some_ability } stub(Person).find(456).stub!.behaviors.stub!.find(789).stub!.abilities.stub!.build(nil) { :some_ability }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456, :behavior_id => 789}, {:nested => [:person, :behavior]}) authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456, :behavior_id => 789}, {:nested => [:person, :behavior]})
@@ -114,4 +125,11 @@ describe CanCan::ResourceAuthorization do
authorization.load_resource authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource @controller.instance_variable_get(:@ability).should == :some_resource
end end
it "should use :name option to determine resource name" do
stub(Ability).find(123) { :some_resource }
authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "foo", :action => "show", :id => 123}, {:name => :ability})
authorization.load_resource
@controller.instance_variable_get(:@ability).should == :some_resource
end
end end