8 Commits
1.4.0 ... 1.4.1

Author SHA1 Message Date
Ryan Bates
872e4cfba8 releasing version 1.4.1 2010-11-12 10:51:29 -08:00
Ryan Bates
787511a208 renaming skip_authorization to skip_authorization_check - closes #169 2010-11-12 10:46:03 -08:00
Ryan Bates
92995d791e adding :through_association option to load_resource (thanks hunterae) - closes #171 2010-11-12 10:42:26 -08:00
Ryan Bates
ebf77ed647 fixing specs due to joins method check in active record additions 2010-11-12 10:31:36 -08:00
Nanda Lopes
9a7c427373 Fix NoMethodError
Raises NoMethodError when using ":singleton => true, :shallow => true" and parent_resource is nil
2010-11-13 02:24:31 +08:00
Ramon Tayag
cf263c105d checks if active record responds to 'joins', so this can work with internuity's quick_scopes gem; added .swp files to git ignore 2010-11-13 02:21:56 +08:00
Michael Halliday
79180de372 This fixes an odd error I was seeing in development mode when cache_classes = false (the default), specifically when loading an object throught the parent in load_and_authorize_resource.
Assume Photo model and User model where user has many photos:

@photo = current_user.photos.find(1) # this returns a photo
@photo1 = Photo.find(1)

@photo.kind_of?(Photo) is not always true for some reason when class_cacheing is false.  Where as @photo1.kind_of?(Photo) always appears to be true.  Of interesting note, in the above example @photo != @photo1 if kind_of? is false.  Very odd.
 
Again, this only appears to be when loading and object through an association.
2010-11-13 02:09:06 +08:00
Ryan Bates
f901c367fc using supermodel in specs to remove some of the model stubs 2010-10-08 11:46:41 -07:00
11 changed files with 109 additions and 54 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
*.swp
**/*.swp
*.gem *.gem
Gemfile.lock Gemfile.lock
.bundle .bundle

View File

@@ -1,3 +1,16 @@
1.4.1 (November 12, 2010)
* Renaming skip_authorization to skip_authorization_check - see issue #169
* Adding :through_association option to load_resource (thanks hunterae) - see issue #171
* The :shallow option now works with the :singleton option (thanks nandalopes) - see issue #187
* Play nicely with quick_scopes gem (thanks ramontayag) - see issue #183
* Fix odd behavior when "cache_classes = false" (thanks mphalliday) - see issue #174
1.4.0 (October 5, 2010) 1.4.0 (October 5, 2010)
* Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163 * Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163

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.4.0" s.version = "1.4.1"
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"
@@ -13,6 +13,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rspec', '~> 2.0.0.beta.22' s.add_development_dependency 'rspec', '~> 2.0.0.beta.22'
s.add_development_dependency 'rails', '~> 3.0.0' s.add_development_dependency 'rails', '~> 3.0.0'
s.add_development_dependency 'rr', '~> 0.10.11' # 1.0.0 has respond_to? issues: http://github.com/btakita/rr/issues/issue/43 s.add_development_dependency 'rr', '~> 0.10.11' # 1.0.0 has respond_to? issues: http://github.com/btakita/rr/issues/issue/43
s.add_development_dependency 'supermodel', '~> 0.1.4'
s.rubyforge_project = s.name s.rubyforge_project = s.name
s.required_rubygems_version = ">= 1.3.4" s.required_rubygems_version = ">= 1.3.4"

View File

@@ -21,7 +21,7 @@ module CanCan
# 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)
query = ability.query(action, self) query = ability.query(action, self)
if respond_to? :where if respond_to?(:where) && respond_to?(:joins)
where(query.conditions).joins(query.joins) where(query.conditions).joins(query.joins)
else else
scoped(:conditions => query.conditions, :joins => query.joins) scoped(:conditions => query.conditions, :joins => query.joins)

View File

@@ -97,7 +97,7 @@ module CanCan
end end
def matches_subject_class?(subject) def matches_subject_class?(subject)
@subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.kind_of?(Module) && subject.ancestors.include?(sub)) } @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
end end
def matches_conditions_hash?(subject, conditions = @conditions) def matches_conditions_hash?(subject, conditions = @conditions)

View File

@@ -71,6 +71,10 @@ module CanCan
# [:+through+] # [:+through+]
# Load this resource through another one. This should match the name of the parent instance variable or method. # Load this resource through another one. This should match the name of the parent instance variable or method.
# #
# [:+through_association+]
# The name of the association to fetch the child records through the parent resource. This is normally not needed
# because it defaults to the pluralized resource name.
#
# [:+shallow+] # [:+shallow+]
# Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+. # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
# #
@@ -172,11 +176,11 @@ module CanCan
# #
# Any arguments are passed to the +after_filter+ it triggers. # Any arguments are passed to the +after_filter+ it triggers.
# #
# See skip_authorization to bypass this check on specific controller actions. # See skip_authorization_check to bypass this check on specific controller actions.
def check_authorization(*args) def check_authorization(*args)
self.after_filter(*args) do |controller| self.after_filter(*args) do |controller|
unless controller.instance_variable_defined?(:@_authorized) unless controller.instance_variable_defined?(:@_authorized)
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization to bypass this check." raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
end end
end end
end end
@@ -184,16 +188,20 @@ module CanCan
# Call this in the class of a controller to skip the check_authorization behavior on the actions. # Call this in the class of a controller to skip the check_authorization behavior on the actions.
# #
# class HomeController < ApplicationController # class HomeController < ApplicationController
# skip_authorization :only => :index # skip_authorization_check :only => :index
# end # end
# #
# Any arguments are passed to the +before_filter+ it triggers. # Any arguments are passed to the +before_filter+ it triggers.
def skip_authorization(*args) def skip_authorization_check(*args)
self.before_filter(*args) do |controller| self.before_filter(*args) do |controller|
controller.instance_variable_set(:@_authorized, true) controller.instance_variable_set(:@_authorized, true)
end end
end end
def skip_authorization(*args)
raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
end
def cancan_resource_class def cancan_resource_class
if ancestors.map(&:to_s).include? "InheritedResources::Actions" if ancestors.map(&:to_s).include? "InheritedResources::Actions"
InheritedResource InheritedResource

View File

@@ -61,7 +61,11 @@ module CanCan
end end
def build_resource def build_resource
resource = resource_base.send(@options[:singleton] ? "build_#{name}" : "new") if @options[:singleton] && resource_base.respond_to?("build_#{name}")
resource = resource_base.send("build_#{name}")
else
resource = resource_base.send("new")
end
initial_attributes.each do |name, value| initial_attributes.each do |name, value|
resource.send("#{name}=", value) resource.send("#{name}=", value)
end end
@@ -74,7 +78,7 @@ module CanCan
end end
def find_resource def find_resource
if @options[:singleton] if @options[:singleton] && resource_base.respond_to?(name)
resource_base.send(name) resource_base.send(name)
else else
@options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param) @options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param)
@@ -132,7 +136,7 @@ module CanCan
def resource_base def resource_base
if @options[:through] if @options[:through]
if parent_resource if parent_resource
@options[:singleton] ? parent_resource : parent_resource.send(name.to_s.pluralize) @options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
elsif @options[:shallow] elsif @options[:shallow]
resource_class resource_class
else else

View File

@@ -10,12 +10,14 @@ describe CanCan::ActiveRecordAdditions do
end end
it "should call where('true=false') when no ability is defined so no records are found" do it "should call where('true=false') when no ability is defined so no records are found" do
stub(@model_class).joins { true } # just so it responds to .joins as well
stub(@model_class).where('true=false').stub!.joins(nil) { :no_match } stub(@model_class).where('true=false').stub!.joins(nil) { :no_match }
@model_class.accessible_by(@ability, :read).should == :no_match @model_class.accessible_by(@ability, :read).should == :no_match
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 => {:bar => 1} @ability.can :read, @model_class, :foo => {:bar => 1}
stub(@model_class).joins { true } # just so it responds to .joins as well
stub(@model_class).where(:foos => { :bar => 1 }).stub!.joins([:foo]) { :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

View File

@@ -54,9 +54,9 @@ describe CanCan::ControllerAdditions do
@controller_class.load_resource :foo => :bar, :only => [:show, :index] @controller_class.load_resource :foo => :bar, :only => [:show, :index]
end end
it "skip_authorization should set up a before filter which sets @_authorized to true" do it "skip_authorization_check should set up a before filter which sets @_authorized to true" do
mock(@controller_class).before_filter(:filter_options) { |options, block| block.call(@controller) } mock(@controller_class).before_filter(:filter_options) { |options, block| block.call(@controller) }
@controller_class.skip_authorization(:filter_options) @controller_class.skip_authorization_check(:filter_options)
@controller.instance_variable_get(:@_authorized).should be_true @controller.instance_variable_get(:@_authorized).should be_true
end end

View File

@@ -10,11 +10,11 @@ describe CanCan::ControllerResource do
end end
it "should load the resource into an instance variable if params[:id] is specified" do it "should load the resource into an instance variable if params[:id] is specified" do
@params.merge!(:action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller) resource = CanCan::ControllerResource.new(@controller)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should not load resource into an instance variable if already set" do it "should not load resource into an instance variable if already set" do
@@ -26,19 +26,19 @@ describe CanCan::ControllerResource do
end end
it "should properly load resource for namespaced controller" do it "should properly load resource for namespaced controller" do
@params.merge!(:controller => "admin/projects", :action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller) resource = CanCan::ControllerResource.new(@controller)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should properly load resource for namespaced controller when using '::' for namespace" do it "should properly load resource for namespaced controller when using '::' for namespace" do
@params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller) resource = CanCan::ControllerResource.new(@controller)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should build a new resource with hash if params[:id] is not specified" do it "should build a new resource with hash if params[:id] is not specified" do
@@ -157,11 +157,11 @@ describe CanCan::ControllerResource do
end end
it "should load parent resource through proper id parameter" do it "should load parent resource through proper id parameter" do
@params.merge!(:action => "index", :project_id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "index", :project_id => project.id)
resource = CanCan::ControllerResource.new(@controller, :project, :parent => true) resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should load resource through the association of another parent resource using instance variable" do it "should load resource through the association of another parent resource using instance variable" do
@@ -174,6 +174,16 @@ describe CanCan::ControllerResource do
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == :some_project
end end
it "should load resource through the custom association name" do
@params.merge!(:action => "show", :id => 123)
category = Object.new
@controller.instance_variable_set(:@category, category)
stub(category).custom_projects.stub!.find(123) { :some_project }
resource = CanCan::ControllerResource.new(@controller, :through => :category, :through_association => :custom_projects)
resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project
end
it "should load resource through the association of another parent resource using method" do it "should load resource through the association of another parent resource using method" do
@params.merge!(:action => "show", :id => 123) @params.merge!(:action => "show", :id => 123)
category = Object.new category = Object.new
@@ -185,16 +195,16 @@ describe CanCan::ControllerResource do
end end
it "should not load through parent resource if instance isn't loaded when shallow" do it "should not load through parent resource if instance isn't loaded when shallow" do
@params.merge!(:action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true) resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should raise AccessDenied when attempting to load resource through nil" do it "should raise AccessDenied when attempting to load resource through nil" do
@params.merge!(:action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :through => :category) resource = CanCan::ControllerResource.new(@controller, :through => :category)
lambda { lambda {
resource.load_resource resource.load_resource
@@ -241,20 +251,35 @@ describe CanCan::ControllerResource do
@controller.instance_variable_get(:@project).name.should == "foobar" @controller.instance_variable_get(:@project).name.should == "foobar"
end end
it "should find record through has_one association with :singleton and :shallow options" do
project = Project.create!
@params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
resource.load_resource
@controller.instance_variable_get(:@project).should == project
end
it "should build record through has_one association with :singleton and :shallow options" do
@params.merge!(:action => "create", :project => {:name => "foobar"})
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
resource.load_resource
@controller.instance_variable_get(:@project).name.should == "foobar"
end
it "should only authorize :read action on parent resource" do it "should only authorize :read action on parent resource" do
@params.merge!(:action => "new", :project_id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "new", :project_id => project.id)
stub(@controller).authorize!(:read, :some_project) { raise CanCan::AccessDenied } stub(@controller).authorize!(:read, project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :project, :parent => true) resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied) lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
end end
it "should load the model using a custom class" do it "should load the model using a custom class" do
@params.merge!(:action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "show", :id => project.id)
resource = CanCan::ControllerResource.new(@controller, :class => Project) resource = CanCan::ControllerResource.new(@controller, :class => Project)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should authorize based on resource name if class is false" do it "should authorize based on resource name if class is false" do
@@ -265,20 +290,20 @@ describe CanCan::ControllerResource do
end end
it "should load and authorize using custom instance name" do it "should load and authorize using custom instance name" do
@params.merge!(:action => "show", :id => 123) project = Project.create!
stub(Project).find(123) { :some_project } @params.merge!(:action => "show", :id => project.id)
stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied } stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project) resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project)
lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied) lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
@controller.instance_variable_get(:@custom_project).should == :some_project @controller.instance_variable_get(:@custom_project).should == project
end end
it "should load resource using custom find_by attribute" do it "should load resource using custom find_by attribute" do
@params.merge!(:action => "show", :id => 123) project = Project.create!(:name => "foo")
stub(Project).find_by_name!(123) { :some_project } @params.merge!(:action => "show", :id => "foo")
resource = CanCan::ControllerResource.new(@controller, :find_by => :name) resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
resource.load_resource resource.load_resource
@controller.instance_variable_get(:@project).should == :some_project @controller.instance_variable_get(:@project).should == project
end end
it "should raise ImplementationRemoved when adding :name option" do it "should raise ImplementationRemoved when adding :name option" do

View File

@@ -1,12 +1,17 @@
require 'rubygems' require 'rubygems'
require 'bundler' require 'bundler/setup'
Bundler.require(:default, :test) Bundler.require(:default)
require 'supermodel' # shouldn't Bundler do this already?
require 'active_support/all' require 'active_support/all'
require 'matchers' require 'matchers'
require 'cancan/matchers' require 'cancan/matchers'
RSpec.configure do |config| RSpec.configure do |config|
config.mock_with :rr config.mock_with :rr
config.before(:each) do
Project.delete_all
Category.delete_all
end
end end
class Ability class Ability
@@ -16,17 +21,12 @@ class Ability
end end
end end
# Generic class to mimic a model class Category < SuperModel::Base
class Project has_many :projects
attr_accessor :name end
def initialize(attributes = {}) class Project < SuperModel::Base
@name = attributes[:name] belongs_to :category
end
def attributes=(attributes)
@name = attributes[:name] if attributes[:name]
end
class << self class << self
protected protected