aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/identity_map.rb
blob: d7ba1d953c3abba83fe330cb9e6e6dc167681d96 (plain) (blame)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
module ActiveRecord
  # = Active Record Identity Map
  #
  # Ensures that each object gets loaded only once by keeping every loaded
  # object in a map. Looks up objects using the map when referring to them.
  #
  # More information on Identity Map pattern:
  #   http://www.martinfowler.com/eaaCatalog/identityMap.html
  #
  # == Configuration
  #
  # In order to disable IdentityMap, set <tt>config.active_record.identity_map = false</tt>
  # in your <tt>config/application.rb</tt> file.
  #
  # IdentityMap is enabled by default.
  #
  module IdentityMap
    extend ActiveSupport::Concern

    class << self
      attr_accessor :repositories
      attr_accessor :current_repository_name
      attr_accessor :enabled

      def current
        repositories[current_repository_name] ||= Hash.new { |h,k| h[k] = Weakling::WeakHash.new }
      end

      def with_repository(name = :default)
        old_repository = self.current_repository_name
        self.current_repository_name = name

        yield if block_given?
      ensure
        self.current_repository_name = old_repository
      end

      def without
        old, self.enabled = self.enabled, false

        yield if block_given?
      ensure
        self.enabled = old
      end

      def get(klass, primary_key)
        if obj = current[klass.symbolized_base_class][primary_key]
          return obj if obj.id == primary_key && klass == obj.class
        end

        nil
      end

      def add(record)
        current[record.class.symbolized_base_class][record.id] = record
      end

      def remove(record)
        current[record.class.symbolized_base_class].delete(record.id)
      end

      def clear
        current.clear
      end

      alias enabled? enabled
      alias identity_map= enabled=
    end

    self.repositories ||= Hash.new
    self.current_repository_name ||= :default
    self.enabled = true

    module InstanceMethods
      # Reinitialize an Identity Map model object from +coder+.
      # +coder+ must contain the attributes necessary for initializing an empty
      # model object.
      def reinit_with(coder)
        @attributes_cache = {}
        dirty = @changed_attributes.keys
        @attributes.update(coder['attributes'].except(*dirty))
        @changed_attributes.update(coder['attributes'].slice(*dirty))
        @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]}

        _run_find_callbacks

        self
      end
    end

    module ClassMethods
      def identity_map
        ActiveRecord::IdentityMap
      end
    end
  end
end