adding initial active record adapter
This commit is contained in:
parent
4c5ba09f4c
commit
af9e77a79e
|
@ -6,6 +6,7 @@ require 'cancan/exceptions'
|
||||||
require 'cancan/query'
|
require 'cancan/query'
|
||||||
require 'cancan/inherited_resource'
|
require 'cancan/inherited_resource'
|
||||||
|
|
||||||
|
require 'cancan/model_adapters/abstract_adapter'
|
||||||
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
|
require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
|
||||||
require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
|
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
|
||||||
|
|
|
@ -186,11 +186,8 @@ module CanCan
|
||||||
@aliased_actions = {}
|
@aliased_actions = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a CanCan::Query instance to help generate database queries based on the ability.
|
def model_adapter(model_class, action)
|
||||||
# If any relevant rules use a block then an exception will be raised because an
|
ModelAdapters::ActiveRecordAdapter.new(model_class, relevant_rules_for_query(action, model_class))
|
||||||
# SQL query cannot be generated from blocks of code.
|
|
||||||
def query(action, subject)
|
|
||||||
Query.new(subject, relevant_rules_for_query(action, subject))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# See ControllerAdditions#authorize! for documentation.
|
# See ControllerAdditions#authorize! for documentation.
|
||||||
|
|
10
lib/cancan/model_adapters/abstract_adapter.rb
Normal file
10
lib/cancan/model_adapters/abstract_adapter.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module CanCan
|
||||||
|
module ModelAdapters
|
||||||
|
class AbstractAdapter
|
||||||
|
def initialize(model_class, rules)
|
||||||
|
@model_class = model_class
|
||||||
|
@rules = rules
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,103 @@
|
||||||
|
module CanCan
|
||||||
|
module ModelAdapters
|
||||||
|
class ActiveRecordAdapter < AbstractAdapter
|
||||||
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
||||||
|
# method directly, but instead go through ActiveRecordAdditions#accessible_by.
|
||||||
|
#
|
||||||
|
# If there is only one "can" definition, a hash of conditions will be returned matching the one defined.
|
||||||
|
#
|
||||||
|
# can :manage, User, :id => 1
|
||||||
|
# query(:manage, User).conditions # => { :id => 1 }
|
||||||
|
#
|
||||||
|
# If there are multiple "can" definitions, a SQL string will be returned to handle complex cases.
|
||||||
|
#
|
||||||
|
# can :manage, User, :id => 1
|
||||||
|
# can :manage, User, :manager_id => 1
|
||||||
|
# cannot :manage, User, :self_managed => true
|
||||||
|
# query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
|
||||||
|
#
|
||||||
|
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
|
||||||
|
else
|
||||||
|
@rules.reverse.inject(false_sql) do |sql, rule|
|
||||||
|
merge_conditions(sql, rule.tableized_conditions.dup, rule.base_behavior)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
joins_hash = {}
|
||||||
|
@rules.each do |rule|
|
||||||
|
merge_joins(joins_hash, rule.associations_hash)
|
||||||
|
end
|
||||||
|
clean_joins(joins_hash) unless joins_hash.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def database_records
|
||||||
|
if @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
||||||
|
@model_class.where(conditions).joins(joins)
|
||||||
|
else
|
||||||
|
@model_class.scoped(:conditions => conditions, :joins => joins)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def merge_conditions(sql, conditions_hash, behavior)
|
||||||
|
if conditions_hash.blank?
|
||||||
|
behavior ? true_sql : false_sql
|
||||||
|
else
|
||||||
|
conditions = sanitize_sql(conditions_hash)
|
||||||
|
case sql
|
||||||
|
when true_sql
|
||||||
|
behavior ? true_sql : "not (#{conditions})"
|
||||||
|
when false_sql
|
||||||
|
behavior ? conditions : false_sql
|
||||||
|
else
|
||||||
|
behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def false_sql
|
||||||
|
sanitize_sql(['?=?', true, false])
|
||||||
|
end
|
||||||
|
|
||||||
|
def true_sql
|
||||||
|
sanitize_sql(['?=?', true, true])
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize_sql(conditions)
|
||||||
|
@model_class.send(:sanitize_sql, conditions)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Takes two hashes and does a deep merge.
|
||||||
|
def merge_joins(base, add)
|
||||||
|
add.each do |name, nested|
|
||||||
|
if base[name].is_a?(Hash) && !nested.empty?
|
||||||
|
merge_joins(base[name], nested)
|
||||||
|
else
|
||||||
|
base[name] = nested
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes empty hashes and moves everything into arrays.
|
||||||
|
def clean_joins(joins_hash)
|
||||||
|
joins = []
|
||||||
|
joins_hash.each do |name, nested|
|
||||||
|
joins << (nested.empty? ? name : {name => clean_joins(nested)})
|
||||||
|
end
|
||||||
|
joins
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module CanCan
|
module CanCan
|
||||||
# This module is automatically included into all Active Record models.
|
# This module is automatically included into all Active Record models.
|
||||||
module ActiveRecordAdditions
|
module ActiveRecordAdditions
|
||||||
|
@ -20,12 +120,7 @@ module CanCan
|
||||||
# Here only the articles which the user can update are returned. This
|
# Here only the articles which the user can update are returned. This
|
||||||
# internally uses Ability#conditions method, see that for more information.
|
# internally uses Ability#conditions method, see that for more information.
|
||||||
def accessible_by(ability, action = :read)
|
def accessible_by(ability, action = :read)
|
||||||
query = ability.query(action, self)
|
ability.model_adapter(self, action).database_records
|
||||||
if respond_to?(:where) && respond_to?(:joins)
|
|
||||||
where(query.conditions).joins(query.joins)
|
|
||||||
else
|
|
||||||
scoped(:conditions => query.conditions, :joins => query.joins)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
|
||||||
describe CanCan::ActiveRecordAdditions do
|
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@model_class = Class.new(Project)
|
@model_class = Class.new(Project)
|
||||||
stub(@model_class).scoped { :scoped_stub }
|
stub(@model_class).scoped { :scoped_stub }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user