adding joins clause to accessible_by when conditions are across associations

This commit is contained in:
Ryan Bates 2010-04-20 17:02:28 -07:00
parent 4da31c0709
commit e20081454f
6 changed files with 68 additions and 7 deletions

View File

@ -1,3 +1,5 @@
* 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

View File

@ -200,6 +200,16 @@ module CanCan
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
private
def can_definitions

View File

@ -21,10 +21,11 @@ module CanCan
# internally uses Ability#conditions method, see that for more information.
def accessible_by(ability, action = :read)
conditions = ability.conditions(action, self) || {:id => nil}
joins = ability.association_joins(action, self)
if respond_to? :where
where(conditions)
where(conditions).joins(joins)
else
scoped(:conditions => conditions)
scoped(:conditions => conditions, :joins => joins)
end
end
end

View File

@ -26,6 +26,21 @@ module CanCan
@base_behavior ? result : !result
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)

View File

@ -10,19 +10,19 @@ describe CanCan::ActiveRecordAdditions do
end
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
end
it "should call where with matching ability conditions" do
@ability.can :read, @model_class, :foo => 1
stub(@model_class).where(:foo => 1) { :found_records }
@ability.can :read, @model_class, :foo => {:bar => 1}
stub(@model_class).where(:foo => { :bar => 1 }).stub!.joins([:foo]) { :found_records }
@model_class.accessible_by(@ability, :read).should == :found_records
end
it "should default to :read ability and use scoped when where isn't available" do
@ability.can :read, @model_class, :foo => 1
stub(@model_class).scoped(:conditions => {:foo => 1}) { :found_records }
@ability.can :read, @model_class, :foo => {:bar => 1}
stub(@model_class).scoped(:conditions => {:foo => {:bar => 1}}, :joins => [:foo]) { :found_records }
@model_class.accessible_by(@ability).should == :found_records
end
end

View File

@ -0,0 +1,33 @@
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
end