From 25a1c553bfefafd3002bddfc67152a27bd0eb3d7 Mon Sep 17 00:00:00 2001 From: Ryan Bates Date: Thu, 5 Aug 2010 16:12:30 -0700 Subject: [PATCH] adding :through option to replace :nesting option and moving ResourceAuthorization class code into ControllerResource --- CHANGELOG.rdoc | 6 + lib/cancan.rb | 1 - lib/cancan/controller_additions.rb | 80 ++++++---- lib/cancan/controller_resource.rb | 121 ++++++++++----- lib/cancan/resource_authorization.rb | 70 --------- spec/cancan/controller_additions_spec.rb | 12 +- spec/cancan/controller_resource_spec.rb | 168 +++++++++++++++++---- spec/cancan/resource_authorization_spec.rb | 135 ----------------- 8 files changed, 292 insertions(+), 301 deletions(-) delete mode 100644 lib/cancan/resource_authorization.rb delete mode 100644 spec/cancan/resource_authorization_spec.rb diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index a9f27239..59ddc205 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,3 +1,9 @@ +* Changing :resource option in load/authorize_resource back to :class with ability to pass false + +* Removing :nested option in favor of :through option with separate load/authorize call + +* Moving internal logic from ResourceAuthorization to ControllerResource class + * Supporting multiple "can" and "cannot" calls with accessible_by (thanks funny-falcon) - see issue #71 * Supporting deeply nested aliases - see issue #98 diff --git a/lib/cancan.rb b/lib/cancan.rb index a6bdce67..b320393a 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -1,7 +1,6 @@ require 'cancan/ability' require 'cancan/can_definition' require 'cancan/controller_resource' -require 'cancan/resource_authorization' require 'cancan/controller_additions' require 'cancan/active_record_additions' require 'cancan/exceptions' diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index 28f3bb47..338e737d 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -11,11 +11,11 @@ module ClassMethods # load_and_authorize_resource # end # - def load_and_authorize_resource(options = {}) - ResourceAuthorization.add_before_filter(self, :load_and_authorize_resource, options) + def load_and_authorize_resource(*args) + ControllerResource.add_before_filter(self, :load_and_authorize_resource, *args) end - # Sets up a before filter which loads the appropriate model resource into an instance variable. + # Sets up a before filter which loads the model resource into an instance variable. # For example, given an ArticlesController it will load the current article into the @article # instance variable. It does this by either calling Article.find(params[:id]) or # Article.new(params[:article]) depending upon the action. It does nothing for the "index" @@ -41,6 +41,20 @@ def load_and_authorize_resource(options = {}) # end # end # + # If a name is provided which does not match the controller it assumes it is a parent resource. Child + # resources can then be loaded through it. + # + # class BooksController < ApplicationController + # load_resource :author + # load_resource :book, :through => :author + # end + # + # Here the author resource will be loaded before each action using params[:author_id]. The book resource + # will then be loaded through the @author instance variable. + # + # That first argument is optional and will default to the singular name of the controller. + # A hash of options (see below) can also be passed to this method to further customize it. + # # See load_and_authorize_resource to automatically authorize the resource too. # # Options: @@ -50,27 +64,22 @@ def load_and_authorize_resource(options = {}) # [:+except+] # Does not apply before filter to given actions. # - # [:+nested+] - # Specify which resource this is nested under. - # - # load_resource :nested => :author - # - # Deep nesting can be defined in an array. - # - # load_resource :nested => [:publisher, :author] - # - # [:+name+] - # The name of the resource if it cannot be determined from controller (string or symbol). + # [:+through+] + # Load this resource through another one. This should match the name of the parent instance variable. # - # load_resource :name => :article + # [:+parent+] + # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource + # name is given which does not match the controller. # - # [:+resource+] + # [:+class+] # The class to use for the model (string or constant). # + # [:+instance_name+] + # The name of the instance variable to load the resource into. + # # [:+collection+] # Specify which actions are resource collection actions in addition to :+index+. This - # is usually not necessary because it will try to guess depending on if an :+id+ - # is present in +params+. + # is usually not necessary because it will try to guess depending on if the id param is present. # # load_resource :collection => [:sort, :list] # @@ -81,11 +90,11 @@ def load_and_authorize_resource(options = {}) # # load_resource :new => :build # - def load_resource(options = {}) - ResourceAuthorization.add_before_filter(self, :load_resource, options) + def load_resource(*args) + ControllerResource.add_before_filter(self, :load_resource, *args) end - # Sets up a before filter which authorizes the current resource using the instance variable. + # Sets up a before filter which authorizes the resource using the instance variable. # For example, if you have an ArticlesController it will check the @article instance variable # and ensure the user can perform the current action on it. Under the hood it is doing # something like the following. @@ -98,6 +107,19 @@ def load_resource(options = {}) # authorize_resource # end # + # If you pass in the name of a resource which does not match the controller it will assume + # it is a parent resource. + # + # class BooksController < ApplicationController + # authorize_resource :author + # authorize_resource :book + # end + # + # Here it will authorize :+show+, @+author+ on every action before authorizing the book. + # + # That first argument is optional and will default to the singular name of the controller. + # A hash of options (see below) can also be passed to this method to further customize it. + # # See load_and_authorize_resource to automatically load the resource too. # # Options: @@ -107,17 +129,23 @@ def load_resource(options = {}) # [:+except+] # Does not apply before filter to given actions. # - # [:+name+] - # The name of the resource if it cannot be determined from controller (string or symbol). + # [:+parent+] + # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource + # name is given which does not match the controller. + # + # [:+class+] + # The class to use for the model (string or constant). This passed in when the instance variable is not set. + # Pass +false+ if there is no associated class for this resource and it will use a symbol of the resource name. # - # load_resource :name => :article + # [:+instance_name+] + # The name of the instance variable for this resource. # # [:+resource+] # The class to use for the model (string or constant). Alternatively pass a symbol # to represent a resource which does not have a class. # - def authorize_resource(options = {}) - ResourceAuthorization.add_before_filter(self, :authorize_resource, options) + def authorize_resource(*args) + ControllerResource.add_before_filter(self, :authorize_resource, *args) end end diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index d73389bc..7cd0984d 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -1,54 +1,105 @@ 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. + # 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 ControllerResource # :nodoc: - 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 - @controller = controller - @name = name - @parent = parent - @options = options - 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 - resource_class = @options[:resource] - if resource_class.nil? - @name.to_s.camelize.constantize - elsif resource_class.kind_of? String - resource_class.constantize - else - resource_class # could be a symbol + def self.add_before_filter(controller_class, method, options = {}) + controller_class.before_filter(options.slice(:only, :except)) do |controller| + ControllerResource.new(controller, controller.params, options.except(:only, :except)).send(method) end end - def find(id) - self.model_instance ||= base.find(id) + def initialize(controller, params, *args) + @controller = controller + @params = params + @options = args.extract_options! + @name = args.first + raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested] + raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name] + raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource] + end + + def load_and_authorize_resource + load_resource + authorize_resource 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) - self.model_instance ||= (base.kind_of?(Class) ? base.new(attributes) : base.build(attributes)) + def load_resource + if !resource_instance && (parent? || member_action?) + @controller.instance_variable_set("@#{name}", load_resource_instance) + end end - def model_instance - @controller.instance_variable_get("@#{@name}") + def authorize_resource + @controller.authorize!(@params[:action].to_sym, resource_instance || resource_class) end - def model_instance=(instance) - @controller.instance_variable_set("@#{@name}", instance) + def parent? + @options[:parent] || @name && @name != name_from_controller.to_sym end private + def load_resource_instance + if !parent? && new_actions.include?(@params[:action].to_sym) + resource_base.kind_of?(Class) ? resource_base.new(attributes) : resource_base.build(attributes) + elsif id_param + resource_base.find(id_param) + end + end + + def attributes + @params[name.to_sym] + end + + def id_param + @params[parent? ? :"#{name}_id" : :id] + end + + def member_action? + !collection_actions.include? @params[:action].to_sym + end + + # Returns the class used for this resource. This can be overriden by the :class option. + # If +false+ is passed in it will use the resource name as a symbol in which case it should + # only be used for authorization, not loading since there's no class to load through. + def resource_class + case @options[:class] + when false then name.to_sym + when nil then name.to_s.camelize.constantize + when String then @options[:class].constantize + else @options[:class] + end + end + + def resource_instance + @controller.instance_variable_get("@#{name}") + end + # 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 - @parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class + # If the :through option is passed it will go through an association on that instance. + def resource_base + through_resource ? through_resource.send(name.to_s.pluralize) : resource_class + end + + # The object to load this resource through. + def through_resource + @options[:through] && @controller.instance_variable_get("@#{@options[:through]}") + end + + def name + @name || name_from_controller + end + + def name_from_controller + @params[:controller].sub("Controller", "").underscore.split('/').last.singularize + end + + def collection_actions + [:index] + [@options[:collection]].flatten + end + + def new_actions + [:new, :create] + [@options[:new]].flatten end end end diff --git a/lib/cancan/resource_authorization.rb b/lib/cancan/resource_authorization.rb deleted file mode 100644 index aa8f7107..00000000 --- a/lib/cancan/resource_authorization.rb +++ /dev/null @@ -1,70 +0,0 @@ -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: - def self.add_before_filter(controller_class, method, options = {}) - controller_class.before_filter(options.slice(:only, :except)) do |controller| - ResourceAuthorization.new(controller, controller.params, options.except(:only, :except)).send(method) - end - end - - def initialize(controller, params, options = {}) - @controller = controller - @params = params - @options = options - end - - def load_and_authorize_resource - load_resource - authorize_resource - end - - def load_resource - if collection_actions.include? @params[:action].to_sym - parent_resource - else - if new_actions.include? @params[:action].to_sym - resource.build(@params[model_name.to_sym]) - elsif @params[:id] - resource.find(@params[:id]) - end - end - end - - def authorize_resource - @controller.authorize!(@params[:action].to_sym, resource.model_instance || resource.model_class) - end - - private - - def resource - @resource ||= ControllerResource.new(@controller, model_name, parent_resource, @options) - end - - def parent_resource - parent = nil - [@options[:nested]].flatten.compact.each do |name| - id = @params["#{name}_id".to_sym] - if id - parent = ControllerResource.new(@controller, name, parent) - parent.find(id) - else - parent = nil - end - end - parent - end - - def model_name - @options[:name] || @params[:controller].sub("Controller", "").underscore.split('/').last.singularize - end - - def collection_actions - [:index] + [@options[:collection]].flatten - end - - def new_actions - [:new, :create] + [@options[:new]].flatten - end - end -end diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index f886c35f..cc09fcb1 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -52,20 +52,20 @@ @controller.cannot?(:foo, :bar).should be_true end - it "load_and_authorize_resource should setup a before filter which passes call to ResourceAuthorization" do - stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource + it "load_and_authorize_resource should setup a before filter which passes call to ControllerResource" do + stub(CanCan::ControllerResource).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) } @controller_class.load_and_authorize_resource :foo => :bar end - it "authorize_resource should setup a before filter which passes call to ResourceAuthorization" do - stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource + it "authorize_resource should setup a before filter which passes call to ControllerResource" do + stub(CanCan::ControllerResource).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) } @controller_class.authorize_resource :foo => :bar, :except => :show end - it "load_resource should setup a before filter which passes call to ResourceAuthorization" do - stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_resource + it "load_resource should setup a before filter which passes call to ControllerResource" do + stub(CanCan::ControllerResource).new(@controller, @controller.params, :foo => :bar).mock!.load_resource mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) } @controller_class.load_resource :foo => :bar, :only => [:show, :index] end diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index ee61e445..736042e4 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -2,58 +2,170 @@ describe CanCan::ControllerResource do before(:each) do - @controller = Object.new + @controller = Object.new # simple stub for now end - it "should determine model class by constantizing give name" do - CanCan::ControllerResource.new(@controller, :ability).model_class.should == Ability + it "should load the resource into an instance variable if params[:id] is specified" do + stub(Ability).find(123) { :some_resource } + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "show", :id => 123) + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource end - it "should fetch model through model class and assign it to the instance" do - stub(Ability).find(123) { :some_ability } - CanCan::ControllerResource.new(@controller, :ability).find(123) + it "should not load resource into an instance variable if already set" do + @controller.instance_variable_set(:@ability, :some_ability) + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "show", :id => 123) + resource.load_resource @controller.instance_variable_get(:@ability).should == :some_ability end - it "should fetch model through parent and assign it to the instance" do - parent = Object.new - stub(parent).model_instance.stub!.abilities.stub!.find(123) { :some_ability } - CanCan::ControllerResource.new(@controller, :ability, parent).find(123) - @controller.instance_variable_get(:@ability).should == :some_ability + it "should properly load resource for namespaced controller" do + stub(Ability).find(123) { :some_resource } + resource = CanCan::ControllerResource.new(@controller, :controller => "admin/abilities", :action => "show", :id => 123) + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource end - it "should build model through model class and assign it to the instance" do - stub(Ability).new(123) { :some_ability } - CanCan::ControllerResource.new(@controller, :ability).build(123) - @controller.instance_variable_get(:@ability).should == :some_ability + it "should properly load resource for namespaced controller when using '::' for namespace" do + stub(Ability).find(123) { :some_resource } + resource = CanCan::ControllerResource.new(@controller, :controller => "Admin::AbilitiesController", :action => "show", :id => 123) + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource + end + + it "should build a new resource with hash if params[:id] is not specified" do + stub(Ability).new(:foo => "bar") { :some_resource } + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "create", :ability => {:foo => "bar"}) + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource + end + + it "should build a new resource even if attribute hash isn't specified" do + stub(Ability).new(nil) { :some_resource } + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "new") + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource end - it "should build model through parent and assign it to the instance" do - parent = Object.new - stub(parent).model_instance.stub!.abilities.stub!.build(123) { :some_ability } - CanCan::ControllerResource.new(@controller, :ability, parent).build(123) + it "should not build a resource when on index action" do + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "index") + resource.load_resource + @controller.instance_variable_get(:@ability).should be_nil + end + + it "should perform authorization using controller action and loaded model" do + @controller.instance_variable_set(:@ability, :some_resource) + stub(@controller).authorize!(:show, :some_resource) { raise CanCan::AccessDenied } + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "show") + lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied) + end + + it "should perform authorization using controller action and non loaded model" do + stub(@controller).authorize!(:show, Ability) { raise CanCan::AccessDenied } + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "show") + lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied) + end + + it "should call load_resource and authorize_resource for load_and_authorize_resource" do + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "show") + mock(resource).load_resource + mock(resource).authorize_resource + resource.load_and_authorize_resource + end + + it "should not build a resource when on custom collection action" do + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "sort"}, {:collection => [:sort, :list]}) + resource.load_resource + @controller.instance_variable_get(:@ability).should be_nil + end + + it "should build a resource when on custom new action even when params[:id] exists" do + stub(Ability).new(nil) { :some_resource } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "build", :id => 123}, {:new => :build}) + resource.load_resource + @controller.instance_variable_get(:@ability).should == :some_resource + end + + it "should not try to load resource for other action if params[:id] is undefined" do + resource = CanCan::ControllerResource.new(@controller, :controller => "abilities", :action => "list") + resource.load_resource + @controller.instance_variable_get(:@ability).should be_nil + end + + it "should be a parent resource when name is provided which doesn't match controller" do + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities"}, :person) + resource.should be_parent + end + + it "should not be a parent resource when name is provided which matches controller" do + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities"}, :ability) + resource.should_not be_parent + end + + it "should be parent if specified in options" do + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities"}, :ability, {:parent => true}) + resource.should be_parent + end + + it "should load parent resource through proper id parameter when supplying a resource with a different name" do + stub(Person).find(123) { :some_person } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "index", :person_id => 123}, :person) + resource.load_resource + @controller.instance_variable_get(:@person).should == :some_person + end + + it "should load parent resource for collection action" do + stub(Person).find(456) { :some_person } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "index", :person_id => 456}, :person) + resource.load_resource + @controller.instance_variable_get(:@person).should == :some_person + end + + it "should load resource through the association of another parent resource" do + person = Object.new + @controller.instance_variable_set(:@person, person) + stub(person).abilities.stub!.find(123) { :some_ability } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:through => :person}) + resource.load_resource @controller.instance_variable_get(:@ability).should == :some_ability end - it "should not load resource if instance variable is already provided" do - @controller.instance_variable_set(:@ability, :some_ability) - CanCan::ControllerResource.new(@controller, :ability).find(123) + it "should not load through parent resource if instance isn't loaded" do + stub(Ability).find(123) { :some_ability } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:through => :person}) + resource.load_resource @controller.instance_variable_get(:@ability).should == :some_ability end - it "should use the model class option if provided" do + it "should load the model using a custom class" do stub(Person).find(123) { :some_resource } - CanCan::ControllerResource.new(@controller, :ability, nil, :resource => Person).find(123) + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:class => Person}) + resource.load_resource @controller.instance_variable_get(:@ability).should == :some_resource end - it "should convert string to constant for resource" do - CanCan::ControllerResource.new(@controller, :ability, nil, :resource => "Person").model_class.should == Person + it "should authorize based on resource name if class is false" do + stub(@controller).authorize!(:show, :ability) { raise CanCan::AccessDenied } + resource = CanCan::ControllerResource.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:class => false}) + lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied) + + end + + it "should raise ImplementationRemoved when adding :name option" do + lambda { + CanCan::ControllerResource.new(@controller, {}, {:name => :foo}) + }.should raise_error(CanCan::ImplementationRemoved) + end + + it "should raise ImplementationRemoved exception when specifying :resource option since it is no longer used" do + lambda { + CanCan::ControllerResource.new(@controller, {}, {:resource => Person}) + }.should raise_error(CanCan::ImplementationRemoved) end - it "should raise an exception when specifying :class option since it is no longer used" do + it "should raise ImplementationRemoved exception when passing :nested option" do lambda { - CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person) + CanCan::ControllerResource.new(@controller, {}, {:nested => :person}) }.should raise_error(CanCan::ImplementationRemoved) end end diff --git a/spec/cancan/resource_authorization_spec.rb b/spec/cancan/resource_authorization_spec.rb deleted file mode 100644 index 6f50b3a2..00000000 --- a/spec/cancan/resource_authorization_spec.rb +++ /dev/null @@ -1,135 +0,0 @@ -require "spec_helper" - -describe CanCan::ResourceAuthorization do - before(:each) do - @controller = Object.new # simple stub for now - end - - it "should load the resource into an instance variable if params[:id] is specified" do - stub(Ability).find(123) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show", :id => 123) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should properly load resource for namespaced controller" do - stub(Ability).find(123) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "admin/abilities", :action => "show", :id => 123) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should properly load resource for namespaced controller when using '::' for namespace" do - stub(Ability).find(123) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "Admin::AbilitiesController", :action => "show", :id => 123) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should build a new resource with hash if params[:id] is not specified" do - stub(Ability).new(:foo => "bar") { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "create", :ability => {:foo => "bar"}) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should build a new resource even if attribute hash isn't specified" do - stub(Ability).new(nil) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "new") - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should not build a resource when on index action" do - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "index") - authorization.load_resource - @controller.instance_variable_get(:@ability).should be_nil - end - - it "should perform authorization using controller action and loaded model" do - @controller.instance_variable_set(:@ability, :some_resource) - stub(@controller).authorize!(:show, :some_resource) { raise CanCan::AccessDenied } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show") - lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied) - end - - it "should perform authorization using controller action and non loaded model" do - stub(@controller).authorize!(:show, Ability) { raise CanCan::AccessDenied } - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show") - lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied) - end - - it "should call load_resource and authorize_resource for load_and_authorize_resource" do - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show") - mock(authorization).load_resource - mock(authorization).authorize_resource - authorization.load_and_authorize_resource - end - - it "should not build a resource when on custom collection action" do - authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "sort"}, {:collection => [:sort, :list]}) - authorization.load_resource - @controller.instance_variable_get(:@ability).should be_nil - end - - it "should build a resource when on custom new action even when params[:id] exists" do - stub(Ability).new(nil) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "build", :id => 123}, {:new => :build}) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - end - - it "should not try to load resource for other action if params[:id] is undefined" do - authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "list") - authorization.load_resource - @controller.instance_variable_get(:@ability).should be_nil - end - - it "should load nested resource and fetch other resource through the association" do - 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.load_resource - @controller.instance_variable_get(:@person).should == person - @controller.instance_variable_get(:@ability).should == :some_ability - 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 - 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.load_resource - @controller.instance_variable_get(:@ability).should == :some_ability - end - - it "should not load nested resource and build through this if *_id param isn't specified" do - stub(Person).find(456) { :some_person } - stub(Ability).new(nil) { :some_ability } - authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456}, {:nested => [:person, :behavior]}) - authorization.load_resource - @controller.instance_variable_get(:@person).should == :some_person - @controller.instance_variable_get(:@ability).should == :some_ability - end - - it "should load the model using a custom class" do - stub(Person).find(123) { :some_resource } - authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123}, {:resource => Person}) - authorization.load_resource - @controller.instance_variable_get(:@ability).should == :some_resource - 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