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 enable IdentityMap, set config.active_record.identity_map = truein your config/application.rbfile.

IdentityMap is disabled by default and still in development (i.e. use it with care).

Associations

Active Record Identity Map does not track associations yet. For example:

comment = @post.comments.first
comment.post = nil
@post.comments.include?(comment) #=> true

Ideally, the example above would return false, removing the comment object from the post association when the association is nullified. This may cause side effects, as in the situation below, if Identity Map is enabled:

Post.has_many :comments, :dependent => :destroy

comment = @post.comments.first
comment.post = nil
comment.save
Post.destroy(@post.id)

Without using Identity Map, the code above will destroy the @post object leaving the comment object intact. However, once we enable Identity Map, the post loaded by Post.destroy is exactly the same object as the object @post. As the object @post still has the comment object in @post.comments, once Identity Map is enabled, the comment object will be accidently removed.

This inconsistency is meant to be fixed in future Rails releases.

Namespace
Methods
A
C
E
G
R
U
W
Instance Public methods
add(record)
# File activerecord/lib/active_record/identity_map.rb, line 92
def add(record)
  repository[record.class.symbolized_sti_name][record.id] = record if contain_all_columns?(record)
end
clear()
# File activerecord/lib/active_record/identity_map.rb, line 104
def clear
  repository.clear
end
enabled()
# File activerecord/lib/active_record/identity_map.rb, line 51
def enabled
  Thread.current[:identity_map_enabled]
end
enabled=(flag)
# File activerecord/lib/active_record/identity_map.rb, line 47
def enabled=(flag)
  Thread.current[:identity_map_enabled] = flag
end
get(klass, primary_key)
# File activerecord/lib/active_record/identity_map.rb, line 77
def get(klass, primary_key)
  record = repository[klass.symbolized_sti_name][primary_key]

  if record.is_a?(klass)
    ActiveSupport::Notifications.instrument("identity.active_record",
      :line => "From Identity Map (id: #{primary_key})",
      :name => "#{klass} Loaded",
      :connection_id => object_id)

    record
  else
    nil
  end
end
reinit_with(coder)

Reinitialize an Identity Map model object from coder. codermust contain the attributes necessary for initializing an empty model object.

# File activerecord/lib/active_record/identity_map.rb, line 118
def reinit_with(coder)
  @attributes_cache = {}
  dirty      = @changed_attributes.keys
  attributes = self.class.initialize_attributes(coder['attributes'].except(*dirty))
  @attributes.update(attributes)
  @changed_attributes.update(coder['attributes'].slice(*dirty))
  @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]}

  run_callbacks :find

  self
end
remove(record)
# File activerecord/lib/active_record/identity_map.rb, line 96
def remove(record)
  repository[record.class.symbolized_sti_name].delete(record.id)
end
remove_by_id(symbolized_sti_name, id)
# File activerecord/lib/active_record/identity_map.rb, line 100
def remove_by_id(symbolized_sti_name, id)
  repository[symbolized_sti_name].delete(id)
end
repository()
# File activerecord/lib/active_record/identity_map.rb, line 56
def repository
  Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} }
end
use()
# File activerecord/lib/active_record/identity_map.rb, line 60
def use
  old, self.enabled = enabled, true

  yield if block_given?
ensure
  self.enabled = old
  clear
end
without()
# File activerecord/lib/active_record/identity_map.rb, line 69
def without
  old, self.enabled = enabled, false

  yield if block_given?
ensure
  self.enabled = old
end