Skip to content
Snippets Groups Projects
Commit af9e77a7 authored by Ryan Bates's avatar Ryan Bates
Browse files

adding initial active record adapter

parent 4c5ba09f
No related branches found
No related tags found
No related merge requests found
......@@ -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.
......
module CanCan
module ModelAdapters
class AbstractAdapter
def initialize(model_class, rules)
@model_class = model_class
@rules = rules
end
end
end
end
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
......
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 }
......
File moved
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment