merging master into 2.0

This commit is contained in:
Ryan Bates 2011-05-19 16:01:06 -04:00
commit e24d5d146b
13 changed files with 109 additions and 29 deletions

View File

@ -1,3 +1,26 @@
1.6.5 (May 18, 2011)
* pass action and subject through AccessDenied exception when :through isn't found - issue #366
* many Mongoid adapter improvements (thanks rahearn, cardagin) - issues #363, #352, #343
* allow :through option to work with private controller methods - issue #360
* ensure Mongoid::Document is defined before loading Mongoid adapter - issue #359
* many DataMapper adapter improvements (thanks emmanuel) - issue #355
* handle checking nil attributes through associations (thanks thatothermitch) - issue #330
* improve scope merging - issue #328
1.6.4 (March 29, 2011)
* Fixed mongoid 'or' error - see issue #322
1.6.3 (March 25, 2011)
* Make sure ActiveRecord::Relation is defined before checking conditions against it so Rails 2 is supported again - see issue #312

View File

@ -10,4 +10,4 @@ require 'cancan/model_adapters/abstract_adapter'
require 'cancan/model_adapters/default_adapter'
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid
require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)

View File

@ -290,7 +290,7 @@ module CanCan
def self.included(base)
base.extend ClassMethods
base.helper_method :can?, :cannot?
base.helper_method :can?, :cannot?, :current_ability
end
# Raises a CanCan::Unauthorized exception if the current_ability cannot

View File

@ -165,7 +165,7 @@ module CanCan
elsif @options[:shallow]
resource_class
else
raise Unauthorized # maybe this should be a record not found error instead?
raise Unauthorized.new(nil, authorization_action, @params[:controller].to_sym) # maybe this should be a record not found error instead?
end
else
resource_class
@ -184,7 +184,7 @@ module CanCan
def fetch_parent(name)
if @controller.instance_variable_defined? "@#{name}"
@controller.instance_variable_get("@#{name}")
elsif @controller.respond_to? name
elsif @controller.respond_to?(name, true)
@controller.send(name)
end
end

View File

@ -87,7 +87,7 @@ module CanCan
def database_records
if override_scope
override_scope
@model_class.scoped.merge(override_scope)
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
@model_class.where(conditions).joins(joins)
else

View File

@ -10,23 +10,22 @@ module CanCan
end
def self.matches_conditions_hash?(subject, conditions)
subject.class.all(:conditions => conditions).include?(subject) # TODO don't use a database query here for performance and other instances
collection = DataMapper::Collection.new(subject.query, [ subject ])
!!collection.first(conditions)
end
def database_records
scope = @model_class.all(:conditions => ["0=1"])
conditions.each do |condition|
scope += @model_class.all(:conditions => condition)
end
scope = @model_class.all(:conditions => ["0 = 1"])
cans, cannots = @rules.partition { |r| r.base_behavior }
return scope if cans.empty?
# apply unions first, then differences. this mean cannot overrides can
cans.each { |r| scope += @model_class.all(:conditions => r.conditions) }
cannots.each { |r| scope -= @model_class.all(:conditions => r.conditions) }
scope
end
def conditions
@rules.map(&:conditions)
end
end
end
end
end # class DataMapper
end # module ModelAdapters
end # module CanCan
DataMapper::Model.class_eval do
include CanCan::ModelAdditions::ClassMethods

View File

@ -6,7 +6,14 @@ module CanCan
end
def self.override_conditions_hash_matching?(subject, conditions)
conditions.any? { |k,v| !k.kind_of?(Symbol) }
conditions.any? do |k,v|
key_is_not_symbol = lambda { !k.kind_of?(Symbol) }
subject_value_is_array = lambda do
subject.respond_to?(k) && subject.send(k).is_a?(Array)
end
key_is_not_symbol.call || subject_value_is_array.call
end
end
def self.matches_conditions_hash?(subject, conditions)
@ -16,16 +23,22 @@ module CanCan
end
def database_records
if @rules.size == 0
if @rules.size == 0
@model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria)
@rules[0].conditions
else
@rules.inject(@model_class.all) do |records, rule|
if rule.conditions.empty?
records
elsif rule.base_behavior
records.or(rule.conditions)
# we only need to process can rules if
# there are no rules with empty conditions
rules = @rules.reject { |rule| rule.conditions.empty? }
process_can_rules = @rules.count == rules.count
rules.inject(@model_class.all) do |records, rule|
if process_can_rules && rule.base_behavior
records.or rule.conditions
elsif !rule.base_behavior
records.excludes rule.conditions
else
records.excludes(rule.conditions)
records
end
end
end
@ -37,4 +50,4 @@ end
# simplest way to add `accessible_by` to all Mongoid Documents
module Mongoid::Document::ClassMethods
include CanCan::ModelAdditions::ClassMethods
end
end

View File

@ -125,7 +125,7 @@ module CanCan
if attribute.kind_of? Array
attribute.any? { |element| matches_conditions_hash? element, value }
else
matches_conditions_hash? attribute, value
attribute && matches_conditions_hash?(attribute, value)
end
elsif value.kind_of?(Array) || value.kind_of?(Range)
value.include? attribute

View File

@ -283,6 +283,13 @@ describe CanCan::Ability do
@ability.should be_fully_authorized(:update, :ranges)
end
it "should not match subjects return nil for methods that must match nested a nested conditions hash" do
mock(object_with_foo = Object.new).foo { :bar }
@ability.can :read, :arrays, :first => { :foo => :bar }
@ability.can?(:read, [object_with_foo]).should be_true
@ability.can?(:read, []).should be_false
end
it "is not fully authorized when attributes are required but not checked in update/create actions" do
@ability.can :access, :users, :name
@ability.authorize! :update, :users

View File

@ -7,7 +7,7 @@ describe CanCan::ControllerAdditions do
@controller = @controller_class.new
stub(@controller).params { @params }
stub(@controller).current_user { :current_user }
mock(@controller_class).helper_method(:can?, :cannot?)
mock(@controller_class).helper_method(:can?, :cannot?, :current_ability)
@controller_class.send(:include, CanCan::ControllerAdditions)
end

View File

@ -237,7 +237,10 @@ describe CanCan::ControllerResource do
resource = CanCan::ControllerResource.new(@controller, :through => :category)
lambda {
resource.load_resource
}.should raise_error(CanCan::Unauthorized)
}.should raise_error(CanCan::Unauthorized) { |exception|
exception.action.should == :show
exception.subject.should == :projects
}
@controller.instance_variable_get(:@project).should be_nil
end

View File

@ -125,6 +125,15 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
Article.accessible_by(@ability).should == [article1]
end
it "should fetch only associated records when using with a scope for conditions" do
@ability.can :read, :articles, Article.where(:secret => true)
category1 = Category.create!(:visible => false)
category2 = Category.create!(:visible => true)
article1 = Article.create!(:secret => true, :category => category1)
article2 = Article.create!(:secret => true, :category => category2)
category1.articles.accessible_by(@ability).should == [article1]
end
it "should raise an exception when trying to merge scope with other conditions" do
@ability.can :read, :articles, :published => true
@ability.can :read, :articles, Article.where(:secret => true)

View File

@ -42,6 +42,15 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
@ability.should be_able_to(:read, model)
end
it "should be able to read hashes when field is array" do
one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three'])
two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five'])
@ability.can :foo, :mongoid_projects, :numbers => 'one'
@ability.should be_able_to(:foo, one_to_three)
@ability.should_not be_able_to(:foo, two_to_five)
end
it "should return [] when no ability is defined so no records are found" do
MongoidProject.create(:title => 'Sir')
MongoidProject.create(:title => 'Lord')
@ -59,6 +68,15 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end
it "should be able to mix empty conditions and hashes" do
@ability.can :read, :mongoid_projects
@ability.can :read, :mongoid_projects, :title => 'Sir'
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
MongoidProject.accessible_by(@ability, :read).count.should == 2
end
it "should return everything when the defined ability is access all" do
@ability.can :access, :all
sir = MongoidProject.create(:title => 'Sir')
@ -68,6 +86,14 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
end
it "should allow a scope for conditions" do
@ability.can :read, :mongoid_projects, MongoidProject.where(:title => 'Sir')
sir = MongoidProject.create(:title => 'Sir')
lord = MongoidProject.create(:title => 'Lord')
dude = MongoidProject.create(:title => 'Dude')
MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
end
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
it "should handle :field.in" do