public
Description: simple identity map for active record. eager loading associations FTL
Clone URL: git://github.com/technoweenie/active_record_context.git
Click here to lend your support to: active_record_context and make a donation at www.pledgie.com !
active_record_context / lib / technoweenie / active_record_context.rb
100644 83 lines (73 sloc) 2.615 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
module Technoweenie
  module ActiveRecordContext
    def self.included(base)
      base.extend ClassMethods
      base.alias_method_chain :reload, :context
      base.alias_method_chain :destroy_without_callbacks, :context
      base.cattr_accessor :context_cache
    end
 
    module ClassMethods
      def self.extended(base)
        class << base
          alias_method_chain :find_every, :context
          alias_method_chain :find_one, :context
        end
      end
      
      # Preloads the record from the given array of IDs. The ids should all be the same type.
      # You can pass an array of active record models if that model belongs to the current one.
      #
      # users = User.find :all
      # Avatar.prefetch users # performs this automatically: users.collect { |user| user.avatar_id }
      #
      def prefetch(ids)
        return [] if ids.blank?
        initial = ids.first
        ids = ids.collect { |record| record.send(prefetch_default) } if initial.respond_to?(prefetch_default)
        ids.compact!
        ids.uniq!
        find :all, :conditions => { :id => ids }
      end
      
      # defaults to the foreign key of the current model
      #
      # Avatar => avatar_id
      def prefetch_default
        @prefetch_default ||= name.foreign_key
      end
      
      def find_every_with_context(options)
        returning find_every_without_context(options) do |records|
          store_in_context records
        end
      end
      
      def find_one_with_context(id, options)
        record = options[:conditions].nil? && cached[id.to_i]
        logger.debug("[Context] #{record ? :Found : :Missed} #{name} ##{id}")
        record ? record : find_one_without_context(id, options)
      end
      
      def cached
        context_cache ? (context_cache[self.base_class] ||= {}) : {}
      end
      
      def store_in_context(records)
        return if context_cache.nil?
        logger.debug "[Context] Storing #{name} records: #{records.collect(&:id).to_sentence}"
        records.inject(cached) do |memo, record|
          memo.update record.id => record
        end
      end
      
      # Enables the context cache inside this block.
      def with_context
        self.context_cache = {}
        yield
      ensure
        self.context_cache = nil
      end
    end
    
    def reload_with_context(options = nil)
      self.class.cached[id.to_i] = nil
      reload_without_context(options)
    end
    
    def destroy_without_callbacks_with_context
      self.class.cached[id.to_i] = nil
      destroy_without_callbacks_without_context
    end
  end
end