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
Gemfile.lock
.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)
* Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163

View File

@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = "cancan"
s.version = "1.4.0"
s.version = "1.4.1"
s.author = "Ryan Bates"
s.email = "ryan@railscasts.com"
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 '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 'supermodel', '~> 0.1.4'
s.rubyforge_project = s.name
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.
def accessible_by(ability, action = :read)
query = ability.query(action, self)
if respond_to? :where
if respond_to?(:where) && respond_to?(:joins)
where(query.conditions).joins(query.joins)
else
scoped(:conditions => query.conditions, :joins => query.joins)

View File

@@ -97,7 +97,7 @@ module CanCan
end
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
def matches_conditions_hash?(subject, conditions = @conditions)

View File

@@ -71,6 +71,10 @@ module CanCan
# [:+through+]
# 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+]
# 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.
#
# 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)
self.after_filter(*args) do |controller|
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
@@ -184,16 +188,20 @@ module CanCan
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
#
# class HomeController < ApplicationController
# skip_authorization :only => :index
# skip_authorization_check :only => :index
# end
#
# 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|
controller.instance_variable_set(:@_authorized, true)
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
if ancestors.map(&:to_s).include? "InheritedResources::Actions"
InheritedResource

View File

@@ -61,7 +61,11 @@ module CanCan
end
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|
resource.send("#{name}=", value)
end
@@ -74,7 +78,7 @@ module CanCan
end
def find_resource
if @options[:singleton]
if @options[:singleton] && resource_base.respond_to?(name)
resource_base.send(name)
else
@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
if @options[:through]
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]
resource_class
else

View File

@@ -10,12 +10,14 @@ describe CanCan::ActiveRecordAdditions do
end
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 }
@model_class.accessible_by(@ability, :read).should == :no_match
end
it "should call where with matching ability conditions" do
@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 }
@model_class.accessible_by(@ability, :read).should == :found_records
end

View File

@@ -54,9 +54,9 @@ describe CanCan::ControllerAdditions do
@controller_class.load_resource :foo => :bar, :only => [:show, :index]
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) }
@controller_class.skip_authorization(:filter_options)
@controller_class.skip_authorization_check(:filter_options)
@controller.instance_variable_get(:@_authorized).should be_true
end

View File

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

View File

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