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/inherited_resource'
|
||||
|
||||
require 'cancan/model_adapters/abstract_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
|
||||
|
|
|
@ -186,11 +186,8 @@ module CanCan
|
|||
@aliased_actions = {}
|
||||
end
|
||||
|
||||
# Returns a CanCan::Query instance to help generate database queries based on the ability.
|
||||
# If any relevant rules use a block then an exception will be raised because an
|
||||
# SQL query cannot be generated from blocks of code.
|
||||
def query(action, subject)
|
||||
Query.new(subject, relevant_rules_for_query(action, subject))
|
||||
def model_adapter(model_class, action)
|
||||
ModelAdapters::ActiveRecordAdapter.new(model_class, relevant_rules_for_query(action, model_class))
|
||||
end
|
||||
|
||||
# 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
|
||||
# This module is automatically included into all Active Record models.
|
||||
module ActiveRecordAdditions
|
||||
|
@ -20,12 +120,7 @@ module CanCan
|
|||
# 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)
|
||||
if respond_to?(:where) && respond_to?(:joins)
|
||||
where(query.conditions).joins(query.joins)
|
||||
else
|
||||
scoped(:conditions => query.conditions, :joins => query.joins)
|
||||
end
|
||||
ability.model_adapter(self, action).database_records
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
require "spec_helper"
|
||||
|
||||
describe CanCan::ActiveRecordAdditions do
|
||||
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
||||
before(:each) do
|
||||
@model_class = Class.new(Project)
|
||||
stub(@model_class).scoped { :scoped_stub }
|
||||
|
|
Loading…
Reference in New Issue
Block a user