Merge branch 'master' of https://github.com/bowsersenior/cancan into bowsersenior-master
This commit is contained in:
commit
a6959c0ab2
|
@ -10,11 +10,14 @@ Gem::Specification.new do |s|
|
|||
s.files = Dir["{lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"]
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency 'rspec', '~> 2.0.0.beta.22'
|
||||
s.add_development_dependency 'rspec', '~> 2.1.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 'supermodel', '~> 0.1.4'
|
||||
|
||||
s.add_development_dependency 'mongoid', '~> 2.0.0.beta.19'
|
||||
s.add_development_dependency 'bson_ext', '~> 1.1'
|
||||
|
||||
s.rubyforge_project = s.name
|
||||
s.required_rubygems_version = ">= 1.3.4"
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'cancan/can_definition'
|
|||
require 'cancan/controller_resource'
|
||||
require 'cancan/controller_additions'
|
||||
require 'cancan/active_record_additions'
|
||||
require 'cancan/mongoid_additions'
|
||||
require 'cancan/exceptions'
|
||||
require 'cancan/query'
|
||||
require 'cancan/inherited_resource'
|
||||
|
|
111
lib/cancan/mongoid_additions.rb
Normal file
111
lib/cancan/mongoid_additions.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
module CanCan
|
||||
|
||||
module Ability
|
||||
# could use alias_method_chain, but it's not worth adding activesupport as a gem dependency
|
||||
alias_method :query_without_mongoid_support, :query
|
||||
def query(action, subject)
|
||||
if Object.const_defined?(:Mongoid) && subject <= CanCan::MongoidAdditions
|
||||
query_with_mongoid_support(action, subject)
|
||||
else
|
||||
query_without_mongoid_support(action, subject)
|
||||
end
|
||||
end
|
||||
|
||||
def query_with_mongoid_support(action, subject)
|
||||
MongoidQuery.new(subject, relevant_can_definitions_for_query(action, subject))
|
||||
end
|
||||
end
|
||||
|
||||
class MongoidQuery
|
||||
def initialize(sanitizer, can_definitions)
|
||||
@sanitizer = sanitizer
|
||||
@can_definitions = can_definitions
|
||||
end
|
||||
|
||||
def conditions
|
||||
if @can_definitions.size == 0
|
||||
false_query
|
||||
else
|
||||
@can_definitions.first.instance_variable_get(:@conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def false_query
|
||||
# this query is sure to return no results
|
||||
{:_id => {'$exists' => false, '$type' => 7}} # type 7 is an ObjectID (default for _id)
|
||||
end
|
||||
end
|
||||
|
||||
# customize to handle Mongoid queries in ability definitions conditions
|
||||
# Mongoid Criteria are simpler to check than normal conditions hashes
|
||||
# When no conditions are given, true should be returned.
|
||||
# The default CanCan behavior relies on the fact that conditions.all? will return true when conditions is empty
|
||||
# The way ruby handles all? for empty hashes can be unexpected:
|
||||
# {}.all?{|a| a == 5}
|
||||
# => true
|
||||
# {}.all?{|a| a != 5}
|
||||
# => true
|
||||
class CanDefinition
|
||||
def matches_conditions_hash_with_mongoid_subject?(subject, conditions = @conditions)
|
||||
if subject.class.include?(Mongoid::Document) && conditions.any?{|k,v| !k.kind_of?(Symbol)}
|
||||
if conditions.empty?
|
||||
true
|
||||
else
|
||||
subject.class.where(conditions).include?(subject) # just use Mongoid's where function
|
||||
end
|
||||
else
|
||||
matches_conditions_hash_without_mongoid_subject? subject, conditions
|
||||
end
|
||||
end
|
||||
|
||||
# could use alias_method_chain, but it's not worth adding activesupport as a gem dependency
|
||||
alias_method :matches_conditions_hash_without_mongoid_subject?, :matches_conditions_hash?
|
||||
alias_method :matches_conditions_hash?, :matches_conditions_hash_with_mongoid_subject?
|
||||
end
|
||||
|
||||
|
||||
|
||||
module MongoidAdditions
|
||||
module ClassMethods
|
||||
# Returns a scope which fetches only the records that the passed ability
|
||||
# can perform a given action on. The action defaults to :read. This
|
||||
# is usually called from a controller and passed the +current_ability+.
|
||||
#
|
||||
# @articles = Article.accessible_by(current_ability)
|
||||
#
|
||||
# Here only the articles which the user is able to read will be returned.
|
||||
# If the user does not have permission to read any articles then an empty
|
||||
# result is returned. Since this is a scope it can be combined with any
|
||||
# other scopes or pagination.
|
||||
#
|
||||
# An alternative action can optionally be passed as a second argument.
|
||||
#
|
||||
# @articles = Article.accessible_by(current_ability, :update)
|
||||
#
|
||||
# Here only the articles which the user can update are returned. This
|
||||
# internally uses Ability#conditions method, see that for more information.
|
||||
def accessible_by(ability, action = :read)
|
||||
query = ability.query(action, self)
|
||||
where(query.conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Info on monkeypatching Mongoid :
|
||||
# http://log.mazniak.org/post/719062325/monkey-patching-activesupport-concern-and-you#footer
|
||||
if defined? Mongoid
|
||||
module Mongoid
|
||||
module Components
|
||||
old_block = @_included_block
|
||||
@_included_block = Proc.new do
|
||||
class_eval(&old_block) if old_block
|
||||
include CanCan::MongoidAdditions
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
166
spec/cancan/mongoid_additions_spec.rb
Normal file
166
spec/cancan/mongoid_additions_spec.rb
Normal file
|
@ -0,0 +1,166 @@
|
|||
require "spec_helper"
|
||||
require 'mongoid'
|
||||
|
||||
class MongoidCategory
|
||||
include Mongoid::Document
|
||||
include CanCan::MongoidAdditions
|
||||
|
||||
references_many :mongoid_projects
|
||||
end
|
||||
|
||||
class MongoidProject
|
||||
include Mongoid::Document
|
||||
include CanCan::MongoidAdditions
|
||||
|
||||
referenced_in :mongoid_category
|
||||
|
||||
class << self
|
||||
protected
|
||||
|
||||
def sanitize_sql(hash_cond)
|
||||
hash_cond
|
||||
end
|
||||
|
||||
def sanitize_hash(hash)
|
||||
hash.map do |name, value|
|
||||
if Hash === value
|
||||
sanitize_hash(value).map{|cond| "#{name}.#{cond}"}
|
||||
else
|
||||
"#{name}=#{value}"
|
||||
end
|
||||
end.flatten
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Mongoid.configure do |config|
|
||||
config.master = Mongo::Connection.new('127.0.0.1', 27017).db("workflow_on_mongoid")
|
||||
end
|
||||
|
||||
describe CanCan::MongoidAdditions do
|
||||
before(:each) do
|
||||
@model_class = MongoidProject
|
||||
@ability = Object.new
|
||||
@ability.extend(CanCan::Ability)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
Mongoid.master.collections.select do |collection|
|
||||
collection.name !~ /system/
|
||||
end.each(&:drop)
|
||||
end
|
||||
|
||||
it "should compare properties on mongoid documents with the " do
|
||||
model = @model_class.new
|
||||
@ability.can :read, @model_class, :id => model.id
|
||||
@ability.should be_able_to :read, model
|
||||
end
|
||||
|
||||
it "should return [] when no ability is defined so no records are found" do
|
||||
@model_class.create :title => 'Sir'
|
||||
@model_class.create :title => 'Lord'
|
||||
@model_class.create :title => 'Dude'
|
||||
|
||||
@model_class.accessible_by(@ability, :read).entries.should == []
|
||||
end
|
||||
|
||||
it "should return the correct records based on the defined ability" do
|
||||
@ability.can :read, @model_class, :title => "Sir"
|
||||
sir = @model_class.create :title => 'Sir'
|
||||
lord = @model_class.create :title => 'Lord'
|
||||
dude = @model_class.create :title => 'Dude'
|
||||
|
||||
@model_class.accessible_by(@ability, :read).should == [sir]
|
||||
end
|
||||
|
||||
it "should return everything when the defined ability is manage all" do
|
||||
@ability.can :manage, :all
|
||||
sir = @model_class.create :title => 'Sir'
|
||||
lord = @model_class.create :title => 'Lord'
|
||||
dude = @model_class.create :title => 'Dude'
|
||||
|
||||
@model_class.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
|
||||
end
|
||||
|
||||
|
||||
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
|
||||
it "should handle :field.in" do
|
||||
obj = @model_class.create :title => 'Sir'
|
||||
@ability.can :read, @model_class, :title.in => ["Sir", "Madam"]
|
||||
@ability.can?(:read, obj).should == true
|
||||
@model_class.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
obj2 = @model_class.create :title => 'Lord'
|
||||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
describe "activates only when there are Criteria in the hash" do
|
||||
it "Calls where on the model class when there are criteria" do
|
||||
obj = @model_class.create :title => 'Bird'
|
||||
@conditions = {:title.nin => ["Fork", "Spoon"]}
|
||||
mock(@model_class).where(@conditions) {[obj]}
|
||||
@ability.can :read, @model_class, @conditions
|
||||
@ability.can?(:read, obj)
|
||||
end
|
||||
it "Calls the base version if there are no mongoid criteria" do
|
||||
obj = @model_class.new :title => 'Bird'
|
||||
@conditions = {:id => obj.id}
|
||||
@ability.can :read, @model_class, @conditions
|
||||
@ability.should be_able_to(:read, obj)
|
||||
end
|
||||
end
|
||||
|
||||
it "should handle :field.nin" do
|
||||
obj = @model_class.create :title => 'Sir'
|
||||
@ability.can :read, @model_class, :title.nin => ["Lord", "Madam"]
|
||||
@ability.can?(:read, obj).should == true
|
||||
@model_class.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
obj2 = @model_class.create :title => 'Lord'
|
||||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.size" do
|
||||
obj = @model_class.create :titles => ['Palatin', 'Margrave']
|
||||
@ability.can :read, @model_class, :titles.size => 2
|
||||
@ability.can?(:read, obj).should == true
|
||||
@model_class.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
obj2 = @model_class.create :titles => ['Palatin', 'Margrave', 'Marquis']
|
||||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.exists" do
|
||||
obj = @model_class.create :titles => ['Palatin', 'Margrave']
|
||||
@ability.can :read, @model_class, :titles.exists => true
|
||||
@ability.can?(:read, obj).should == true
|
||||
@model_class.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
obj2 = @model_class.create
|
||||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
|
||||
it "should handle :field.gt" do
|
||||
obj = @model_class.create :age => 50
|
||||
@ability.can :read, @model_class, :age.gt => 45
|
||||
@ability.can?(:read, obj).should == true
|
||||
@model_class.accessible_by(@ability, :read).should == [obj]
|
||||
|
||||
obj2 = @model_class.create :age => 40
|
||||
@ability.can?(:read, obj2).should == false
|
||||
end
|
||||
end
|
||||
|
||||
it "should call where with matching ability conditions" do
|
||||
obj = @model_class.create :foo => {:bar => 1}
|
||||
@ability.can :read, @model_class, :foo => {:bar => 1}
|
||||
@model_class.accessible_by(@ability, :read).entries.first.should == obj
|
||||
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)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user