csdn-dev/second_level_cache

View on GitHub
lib/second_level_cache/active_record/finder_methods.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# -*- encoding : utf-8 -*-
require 'second_level_cache/arel/wheres'

module SecondLevelCache
  module ActiveRecord
    module FinderMethods
      extend ActiveSupport::Concern

      included do
        class_eval do
          alias_method_chain :find_one, :second_level_cache
          alias_method_chain :find_by_attributes, :second_level_cache
        end
      end

      # TODO fetch multi ids
      #
      # Cacheable:
      #
      #     current_user.articles.where(:status => 1).visiable.find(params[:id])
      #
      # Uncacheable:
      #
      #     Article.where("user_id = '1'").find(params[:id])
      #     Article.where("user_id > 1").find(params[:id])
      #     Article.where("articles.user_id = 1").find(prams[:id])
      #     Article.where("user_id = 1 AND ...").find(params[:id])
      def find_one_with_second_level_cache(id)
        return find_one_without_second_level_cache(id) unless second_level_cache_enabled?
        return find_one_without_second_level_cache(id) unless select_all_column?

        id = id.id if ActiveRecord::Base === id
        if ::ActiveRecord::IdentityMap.enabled? && cachable? && record = from_identity_map(id)
          return record
        end

        if cachable?
          return record if record = @klass.read_second_level_cache(id)
        end

        if cachable_without_conditions?
          if record = @klass.read_second_level_cache(id)
            return record if where_match_with_cache?(where_values, record)
          end
        end

        record = find_one_without_second_level_cache(id)
        record.write_second_level_cache
        record
      end

      # TODO cache find_or_create_by_id
      def find_by_attributes_with_second_level_cache(match, attributes, *args)
        return find_by_attributes_without_second_level_cache(match, attributes, *args) unless second_level_cache_enabled?
        return find_by_attributes_without_second_level_cache(match, attributes, *args) unless select_all_column?

        conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]

        if conditions.has_key?("id")
          result = wrap_bang(match.bang?) do
            if conditions.size == 1
              find_one_with_second_level_cache(conditions["id"])
            else
              where(conditions.except("id")).find_one_with_second_level_cache(conditions["id"])
            end
          end
          yield(result) if block_given? #edge rails do this bug rails3.1.0 not

          return result
        end

        find_by_attributes_without_second_level_cache(match, attributes, *args)
      end

      private

      def wrap_bang(bang)
        bang ? yield : (yield rescue nil)
      end

      def cachable?
        where_values.blank? &&
          limit_one? && order_values.blank? &&
          includes_values.blank? && preload_values.blank? &&
          readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
      end

      def cachable_without_conditions?
        limit_one? && order_values.blank? &&
          includes_values.blank? && preload_values.blank? &&
          readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
      end

      def where_match_with_cache?(where_values, cache_record)
        condition = SecondLevelCache::Arel::Wheres.new(where_values)
        return false unless condition.all_equality?
        condition.extract_pairs.all? do |pair|
          cache_record.read_attribute(pair[:left]) == pair[:right]
        end
      end

      def limit_one?
        limit_value.blank? || limit_value == 1
      end

      def select_all_column?
        select_values.blank?
      end

      def from_identity_map(id)
        ::ActiveRecord::IdentityMap.get(@klass, id)
      end
    end
  end
end