fixing active record adapter behavior and improving specs for it
This commit is contained in:
parent
af9e77a79e
commit
cc30e838c0
2
Gemfile
2
Gemfile
|
@ -2,7 +2,9 @@ source "http://rubygems.org"
|
|||
|
||||
case ENV["MODEL_ADAPTER"]
|
||||
when nil, "active_record"
|
||||
gem "sqlite3-ruby", :require => "sqlite3"
|
||||
gem "activerecord", :require => "active_record"
|
||||
gem "with_model"
|
||||
when "data_mapper"
|
||||
gem "dm-core", "~> 1.0.2"
|
||||
when "mongoid"
|
||||
|
|
|
@ -19,14 +19,26 @@ module CanCan
|
|||
def conditions
|
||||
if @rules.size == 1 && @rules.first.base_behavior
|
||||
# Return the conditions directly if there's just one definition
|
||||
@rules.first.tableized_conditions.dup
|
||||
tableized_conditions(@rules.first.conditions).dup
|
||||
else
|
||||
@rules.reverse.inject(false_sql) do |sql, rule|
|
||||
merge_conditions(sql, rule.tableized_conditions.dup, rule.base_behavior)
|
||||
merge_conditions(sql, tableized_conditions(rule.conditions).dup, rule.base_behavior)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tableized_conditions(conditions)
|
||||
return conditions unless conditions.kind_of? Hash
|
||||
conditions.inject({}) do |result_hash, (name, value)|
|
||||
if value.kind_of? Hash
|
||||
name = @model_class.reflect_on_association(name).table_name
|
||||
value = tableized_conditions(value)
|
||||
end
|
||||
result_hash[name] = value
|
||||
result_hash
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the associations used in conditions for the :joins option of a search.
|
||||
# See ActiveRecordAdditions#accessible_by for use in Active Record.
|
||||
def joins
|
||||
|
|
|
@ -3,7 +3,7 @@ module CanCan
|
|||
# it holds the information about a "can" call made on Ability and provides
|
||||
# helpful methods to determine permission checking and conditions hash generation.
|
||||
class Rule # :nodoc:
|
||||
attr_reader :base_behavior, :actions
|
||||
attr_reader :base_behavior, :actions, :conditions
|
||||
attr_writer :expanded_actions
|
||||
|
||||
# The first argument when initializing is the base_behavior which is a true/false
|
||||
|
@ -41,18 +41,6 @@ module CanCan
|
|||
end
|
||||
end
|
||||
|
||||
def tableized_conditions(conditions = @conditions)
|
||||
return conditions unless conditions.kind_of? Hash
|
||||
conditions.inject({}) do |result_hash, (name, value)|
|
||||
if value.kind_of? Hash
|
||||
name = name.to_s.tableize.to_sym
|
||||
value = tableized_conditions(value)
|
||||
end
|
||||
result_hash[name] = value
|
||||
result_hash
|
||||
end
|
||||
end
|
||||
|
||||
def only_block?
|
||||
conditions_empty? && !@block.nil?
|
||||
end
|
||||
|
|
|
@ -1,77 +1,175 @@
|
|||
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
require "spec_helper"
|
||||
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
||||
|
||||
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
||||
with_model :article do
|
||||
table do |t|
|
||||
t.boolean "published"
|
||||
t.boolean "secret"
|
||||
end
|
||||
model do
|
||||
has_many :comments
|
||||
end
|
||||
end
|
||||
|
||||
with_model :comment do
|
||||
table do |t|
|
||||
t.boolean "spam"
|
||||
t.integer "article_id"
|
||||
end
|
||||
model do
|
||||
belongs_to :article
|
||||
end
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@model_class = Class.new(Project)
|
||||
stub(@model_class).scoped { :scoped_stub }
|
||||
@model_class.send(:include, CanCan::ActiveRecordAdditions)
|
||||
Article.delete_all
|
||||
Comment.delete_all
|
||||
@ability = Object.new
|
||||
@ability.extend(CanCan::Ability)
|
||||
@article_table = Article.table_name
|
||||
@comment_table = Comment.table_name
|
||||
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
|
||||
it "should not fetch any records when no abilities are defined" do
|
||||
Article.create!
|
||||
Article.accessible_by(@ability).should be_empty
|
||||
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
|
||||
it "should fetch all articles when one can read all" do
|
||||
@ability.can :read, Article
|
||||
article = Article.create!
|
||||
Article.accessible_by(@ability).should == [article]
|
||||
end
|
||||
|
||||
it "should default to :read ability and use scoped when where isn't available" do
|
||||
@ability.can :read, @model_class, :foo => {:bar => 1}
|
||||
stub(@model_class).scoped(:conditions => {:foos => {:bar => 1}}, :joins => [:foo]) { :found_records }
|
||||
@model_class.accessible_by(@ability).should == :found_records
|
||||
it "should fetch only the articles that are published" do
|
||||
@ability.can :read, Article, :published => true
|
||||
article1 = Article.create!(:published => true)
|
||||
article2 = Article.create!(:published => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should merge association joins and sanitize conditions" do
|
||||
@ability.can :read, @model_class, :foo => {:bar => 1}
|
||||
@ability.can :read, @model_class, :too => {:car => 1, :far => {:bar => 1}}
|
||||
|
||||
condition_variants = [
|
||||
'(toos.fars.bar=1 AND toos.car=1) OR (foos.bar=1)', # faked sql sanitizer is stupid ;-)
|
||||
'(toos.car=1 AND toos.fars.bar=1) OR (foos.bar=1)'
|
||||
]
|
||||
joins_variants = [
|
||||
[:foo, {:too => [:far]}],
|
||||
[{:too => [:far]}, :foo]
|
||||
]
|
||||
|
||||
condition_variants.each do |condition|
|
||||
joins_variants.each do |joins|
|
||||
stub(@model_class).scoped( :conditions => condition, :joins => joins ) { :found_records }
|
||||
end
|
||||
end
|
||||
# @ability.conditions(:read, @model_class).should == '(too.car=1 AND too.far.bar=1) OR (foo.bar=1)'
|
||||
# @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
|
||||
@model_class.accessible_by(@ability).should == :found_records
|
||||
it "should fetch any articles which are published or secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, :secret => true
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
article4 = Article.create!(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1, article2, article3]
|
||||
end
|
||||
|
||||
it "should allow to define sql conditions by not hash" do
|
||||
@ability.can :read, @model_class, :foo => 1
|
||||
@ability.can :read, @model_class, ['bar = ?', 1]
|
||||
stub(@model_class).scoped( :conditions => '(bar = 1) OR (foo=1)', :joins => nil ) { :found_records }
|
||||
stub(@model_class).scoped{|*args| args.inspect}
|
||||
@model_class.accessible_by(@ability).should == :found_records
|
||||
it "should fetch only the articles that are published and not secret" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.cannot :read, Article, :secret => true
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
article4 = Article.create!(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should only read comments for articles which are published" do
|
||||
@ability.can :read, Comment, :article => { :published => true }
|
||||
comment1 = Comment.create!(:article => Article.create!(:published => true))
|
||||
comment2 = Comment.create!(:article => Article.create!(:published => false))
|
||||
Comment.accessible_by(@ability).should == [comment1]
|
||||
end
|
||||
|
||||
it "should allow conditions in SQL and merge with hash conditions" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article4 = Article.create!(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, @model_class do false end
|
||||
lambda {
|
||||
@model_class.accessible_by(@ability)
|
||||
}.should raise_error(CanCan::Error)
|
||||
@ability.can :read, Article do
|
||||
false
|
||||
end
|
||||
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
|
||||
end
|
||||
|
||||
it "should not allow to check ability on object when nonhash sql ability definition without block present" do
|
||||
@ability.can :read, @model_class, ['bar = ?', 1]
|
||||
lambda {
|
||||
@ability.can? :read, @model_class.new
|
||||
}.should raise_error(CanCan::Error)
|
||||
it "should not allow to check ability on object against SQL conditions without block" do
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
|
||||
end
|
||||
|
||||
it "should have false conditions if no abilities match" do
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return false conditions for cannot clause" do
|
||||
@ability.cannot :read, Article
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return SQL for single `can` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Article
|
||||
@ability.can :read, Article, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
|
||||
end
|
||||
|
||||
it "should return true condition for single `can` definition in front of default `can` condition" do
|
||||
@ability.can :read, Article
|
||||
@ability.can :read, Article, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
||||
end
|
||||
|
||||
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Article
|
||||
@ability.cannot :read, Article, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
||||
end
|
||||
|
||||
it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
||||
@ability.can :read, Article
|
||||
@ability.cannot :read, Article, :published => false, :secret => true
|
||||
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
|
||||
end
|
||||
|
||||
it "should return appropriate sql conditions in complex case" do
|
||||
@ability.can :read, Article
|
||||
@ability.can :manage, Article, :id => 1
|
||||
@ability.can :update, Article, :published => true
|
||||
@ability.cannot :update, Article, :secret => true
|
||||
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
|
||||
@ability.model_adapter(Article, :manage).conditions.should == {:id => 1}
|
||||
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
||||
end
|
||||
|
||||
it "should not forget conditions when calling with SQL string" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ['secret=?', false]
|
||||
adapter = @ability.model_adapter(Article, :read)
|
||||
2.times do
|
||||
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
|
||||
end
|
||||
end
|
||||
|
||||
it "should have nil joins if no rules" do
|
||||
@ability.model_adapter(Article, :read).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should have nil joins if no nested hashes specified in conditions" do
|
||||
@ability.can :read, Article, :published => false
|
||||
@ability.can :read, Article, :secret => true
|
||||
@ability.model_adapter(Article, :read).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should merge separate joins into a single array" do
|
||||
@ability.can :read, Article, :project => { :blocked => false }
|
||||
@ability.can :read, Article, :company => { :admin => true }
|
||||
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
|
||||
end
|
||||
|
||||
it "should merge same joins into a single array" do
|
||||
@ability.can :read, Article, :project => { :blocked => false }
|
||||
@ability.can :read, Article, :project => { :admin => true }
|
||||
@ability.model_adapter(Article, :read).joins.should == [:project]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe CanCan::Query do
|
||||
before(:each) do
|
||||
@ability = Object.new
|
||||
@ability.extend(CanCan::Ability)
|
||||
end
|
||||
|
||||
it "should have false conditions if no abilities match" do
|
||||
@ability.query(:destroy, Project).conditions.should == "true=false"
|
||||
end
|
||||
|
||||
it "should return hash for single `can` definition" do
|
||||
@ability.can :read, Project, :blocked => false, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should == { :blocked => false, :user_id => 1 }
|
||||
end
|
||||
|
||||
it "should merge multiple rules into single SQL string joining with OR" do
|
||||
@ability.can :read, Project, :blocked => false
|
||||
@ability.can :read, Project, :admin => true
|
||||
@ability.query(:read, Project).conditions.should == "(admin=true) OR (blocked=false)"
|
||||
end
|
||||
|
||||
it "should merge multiple rules into single SQL string joining with OR and AND" do
|
||||
@ability.can :read, Project, :blocked => false, :active => true
|
||||
@ability.can :read, Project, :admin => true
|
||||
@ability.query(:read, Project).conditions.should orderlessly_match("(blocked=false AND active=true) OR (admin=true)")
|
||||
end
|
||||
|
||||
it "should merge multiple rules into single SQL string joining with OR and AND" do
|
||||
@ability.can :read, Project, :blocked => false, :active => true
|
||||
@ability.can :read, Project, :admin => true
|
||||
@ability.query(:read, Project).conditions.should orderlessly_match("(blocked=false AND active=true) OR (admin=true)")
|
||||
end
|
||||
|
||||
it "should return false conditions for cannot clause" do
|
||||
@ability.cannot :read, Project
|
||||
@ability.query(:read, Project).conditions.should == "true=false"
|
||||
end
|
||||
|
||||
it "should return SQL for single `can` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Project
|
||||
@ability.can :read, Project, :blocked => false, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should orderlessly_match("blocked=false AND user_id=1")
|
||||
end
|
||||
|
||||
it "should return true condition for single `can` definition in front of default `can` condition" do
|
||||
@ability.can :read, Project
|
||||
@ability.can :read, Project, :blocked => false, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should == 'true=true'
|
||||
end
|
||||
|
||||
it "should return false condition for single `cannot` definition" do
|
||||
@ability.cannot :read, Project, :blocked => true, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should == 'true=false'
|
||||
end
|
||||
|
||||
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
||||
@ability.cannot :read, Project
|
||||
@ability.cannot :read, Project, :blocked => true, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should == 'true=false'
|
||||
end
|
||||
|
||||
it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
||||
@ability.can :read, Project
|
||||
@ability.cannot :read, Project, :blocked => true, :user_id => 1
|
||||
@ability.query(:read, Project).conditions.should orderlessly_match("not (blocked=true AND user_id=1)")
|
||||
end
|
||||
|
||||
it "should return appropriate sql conditions in complex case" do
|
||||
@ability.can :read, Project
|
||||
@ability.can :manage, Project, :id => 1
|
||||
@ability.can :update, Project, :manager_id => 1
|
||||
@ability.cannot :update, Project, :self_managed => true
|
||||
@ability.query(:update, Project).conditions.should == 'not (self_managed=true) AND ((manager_id=1) OR (id=1))'
|
||||
@ability.query(:manage, Project).conditions.should == {:id=>1}
|
||||
@ability.query(:read, Project).conditions.should == 'true=true'
|
||||
end
|
||||
|
||||
it "should have nil joins if no rules" do
|
||||
@ability.query(:read, Project).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should have nil joins if no nested hashes specified in conditions" do
|
||||
@ability.can :read, Project, :blocked => false
|
||||
@ability.can :read, Project, :admin => true
|
||||
@ability.query(:read, Project).joins.should be_nil
|
||||
end
|
||||
|
||||
it "should merge separate joins into a single array" do
|
||||
@ability.can :read, Project, :project => { :blocked => false }
|
||||
@ability.can :read, Project, :company => { :admin => true }
|
||||
@ability.query(:read, Project).joins.inspect.should orderlessly_match([:company, :project].inspect)
|
||||
end
|
||||
|
||||
it "should merge same joins into a single array" do
|
||||
@ability.can :read, Project, :project => { :blocked => false }
|
||||
@ability.can :read, Project, :project => { :admin => true }
|
||||
@ability.query(:read, Project).joins.should == [:project]
|
||||
end
|
||||
|
||||
it "should merge complex, nested joins" do
|
||||
@ability.can :read, Project, :project => { :bar => {:test => true} }, :company => { :bar => {:test => true} }
|
||||
@ability.can :read, Project, :project => { :foo => {:bar => true}, :bar => {:zip => :zap} }
|
||||
@ability.query(:read, Project).joins.inspect.should orderlessly_match([{:project => [:bar, :foo]}, {:company => [:bar]}].inspect)
|
||||
end
|
||||
|
||||
it "should not forget conditions when calling with SQL string" do
|
||||
@ability.can :read, Project, :foo => 1
|
||||
@ability.can :read, Project, ['bar = ?', 1]
|
||||
query = @ability.query(:read, Project)
|
||||
2.times do
|
||||
query.conditions.should == "(bar = 1) OR (foo=1)"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,24 +32,6 @@ describe CanCan::Rule do
|
|||
@rule.associations_hash.should == {:foo => {:bar => {}}}
|
||||
end
|
||||
|
||||
it "should tableize correctly for absurdly complex permissions" do
|
||||
@conditions[:unit] = {:property=>{:landlord=>{:weasle_id=>560}}}
|
||||
@conditions[:test] = 1
|
||||
@rule.tableized_conditions.should == {:units => {:properties => {:landlords=>{:weasle_id=>560}}}, :test => 1}
|
||||
end
|
||||
|
||||
it "should tableize correctly for complex permissions" do
|
||||
@conditions[:unit] = {:property=>{:landlord_id=>560}}
|
||||
@conditions[:test] = 1
|
||||
@rule.tableized_conditions.should == {:units => {:properties => {:landlord_id=>560}}, :test => 1}
|
||||
end
|
||||
|
||||
it "should return table names in conditions for association joins" do
|
||||
@conditions[:foo] = {:bar => 1}
|
||||
@conditions[:test] = 1
|
||||
@rule.tableized_conditions.should == {:foos => {:bar => 1}, :test => 1}
|
||||
end
|
||||
|
||||
it "should return no association joins if conditions is nil" do
|
||||
rule = CanCan::Rule.new(true, :read, Integer, nil, nil)
|
||||
rule.associations_hash.should == {}
|
||||
|
|
|
@ -14,6 +14,7 @@ RSpec.configure do |config|
|
|||
Project.delete_all
|
||||
Category.delete_all
|
||||
end
|
||||
config.extend WithModel if defined? WithModel
|
||||
end
|
||||
|
||||
class Ability
|
||||
|
|
Loading…
Reference in New Issue
Block a user