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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user