mirror of https://github.com/rails/rails
Remove IdentityMap
This commit is contained in:
parent
c1f397f82c
commit
a8dd21d8b4
|
@ -1,5 +1,7 @@
|
|||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Remove IdentityMap *Carlos Antonio da Silva*
|
||||
|
||||
* Added the schema cache dump feature.
|
||||
|
||||
`Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
|
||||
|
|
|
@ -26,13 +26,6 @@ You can run all the tests for a given database via rake:
|
|||
|
||||
The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql.
|
||||
|
||||
== Identity Map
|
||||
|
||||
By default the tests run with the Identity Map turned off. But all tests should pass whether or
|
||||
not the identity map is on or off. You can turn it on using the IM env variable:
|
||||
|
||||
$ IM=true ruby -Itest test/case/base_test.rb
|
||||
|
||||
== Config file
|
||||
|
||||
By default, the config file is expected to be at the path test/config.yml. You can specify a
|
||||
|
|
|
@ -65,7 +65,6 @@ module ActiveRecord
|
|||
autoload :DynamicFinderMatch
|
||||
autoload :DynamicScopeMatch
|
||||
autoload :Explain
|
||||
autoload :IdentityMap
|
||||
autoload :Inheritance
|
||||
autoload :Integration
|
||||
autoload :Migration
|
||||
|
|
|
@ -45,7 +45,6 @@ module ActiveRecord
|
|||
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
||||
def reset
|
||||
@loaded = false
|
||||
IdentityMap.remove(target) if IdentityMap.enabled? && target
|
||||
@target = nil
|
||||
end
|
||||
|
||||
|
@ -135,17 +134,7 @@ module ActiveRecord
|
|||
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
||||
# not reraised. The proxy is \reset and +nil+ is the return value.
|
||||
def load_target
|
||||
if find_target?
|
||||
begin
|
||||
if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
|
||||
@target = IdentityMap.get(association_class, owner[reflection.foreign_key])
|
||||
end
|
||||
rescue NameError
|
||||
nil
|
||||
ensure
|
||||
@target ||= find_target
|
||||
end
|
||||
end
|
||||
@target ||= find_target if find_target?
|
||||
loaded! unless loaded?
|
||||
target
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
|
|
|
@ -22,8 +22,6 @@ module ActiveRecord
|
|||
if status = super
|
||||
@previously_changed = changes
|
||||
@changed_attributes.clear
|
||||
elsif IdentityMap.enabled?
|
||||
IdentityMap.remove(self)
|
||||
end
|
||||
status
|
||||
end
|
||||
|
@ -34,9 +32,6 @@ module ActiveRecord
|
|||
@previously_changed = changes
|
||||
@changed_attributes.clear
|
||||
end
|
||||
rescue
|
||||
IdentityMap.remove(self) if IdentityMap.enabled?
|
||||
raise
|
||||
end
|
||||
|
||||
# <tt>reload</tt> the record and clears changed attributes.
|
||||
|
|
|
@ -328,7 +328,6 @@ module ActiveRecord
|
|||
autosave = reflection.options[:autosave]
|
||||
|
||||
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
||||
begin
|
||||
records.each do |record|
|
||||
next if record.destroyed?
|
||||
|
||||
|
@ -348,11 +347,6 @@ module ActiveRecord
|
|||
|
||||
raise ActiveRecord::Rollback unless saved
|
||||
end
|
||||
rescue
|
||||
records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
|
||||
raise
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# reconstruct the scope now that we know the owner's id
|
||||
|
|
|
@ -69,8 +69,6 @@ module ActiveRecord
|
|||
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
|
||||
end
|
||||
|
||||
IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
|
||||
|
||||
update_all(updates.join(', '), primary_key => id)
|
||||
end
|
||||
|
||||
|
|
|
@ -796,9 +796,7 @@ module ActiveRecord
|
|||
@fixture_cache[fixture_name].delete(fixture) if force_reload
|
||||
|
||||
if @loaded_fixtures[fixture_name][fixture.to_s]
|
||||
ActiveRecord::IdentityMap.without do
|
||||
@fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
|
||||
end
|
||||
@fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
|
||||
else
|
||||
raise StandardError, "No entry named '#{fixture}' found for fixture collection '#{fixture_name}'"
|
||||
end
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
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 enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
|
||||
# in your <tt>config/application.rb</tt> file.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
module IdentityMap
|
||||
|
||||
class << self
|
||||
def enabled=(flag)
|
||||
Thread.current[:identity_map_enabled] = flag
|
||||
end
|
||||
|
||||
def enabled
|
||||
Thread.current[:identity_map_enabled]
|
||||
end
|
||||
alias enabled? enabled
|
||||
|
||||
def repository
|
||||
Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} }
|
||||
end
|
||||
|
||||
def use
|
||||
old, self.enabled = enabled, true
|
||||
|
||||
yield if block_given?
|
||||
ensure
|
||||
self.enabled = old
|
||||
clear
|
||||
end
|
||||
|
||||
def without
|
||||
old, self.enabled = enabled, false
|
||||
|
||||
yield if block_given?
|
||||
ensure
|
||||
self.enabled = old
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def add(record)
|
||||
repository[record.class.symbolized_sti_name][record.id] = record
|
||||
end
|
||||
|
||||
def remove(record)
|
||||
repository[record.class.symbolized_sti_name].delete(record.id)
|
||||
end
|
||||
|
||||
def remove_by_id(symbolized_sti_name, id)
|
||||
repository[symbolized_sti_name].delete(id)
|
||||
end
|
||||
|
||||
def clear
|
||||
repository.clear
|
||||
end
|
||||
end
|
||||
|
||||
# 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 = 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
|
||||
|
||||
class Middleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
enabled = IdentityMap.enabled
|
||||
IdentityMap.enabled = true
|
||||
|
||||
response = @app.call(env)
|
||||
response[2] = Rack::BodyProxy.new(response[2]) do
|
||||
IdentityMap.enabled = enabled
|
||||
IdentityMap.clear
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -63,26 +63,9 @@ module ActiveRecord
|
|||
# single-table inheritance model that makes it possible to create
|
||||
# objects of different types from the same table.
|
||||
def instantiate(record, column_types = {})
|
||||
sti_class = find_sti_class(record[inheritance_column])
|
||||
record_id = sti_class.primary_key && record[sti_class.primary_key]
|
||||
|
||||
if ActiveRecord::IdentityMap.enabled? && record_id
|
||||
if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
|
||||
record_id = record_id.to_i
|
||||
end
|
||||
if instance = IdentityMap.get(sti_class, record_id)
|
||||
instance.reinit_with('attributes' => record)
|
||||
else
|
||||
instance = sti_class.allocate.init_with('attributes' => record)
|
||||
IdentityMap.add(instance)
|
||||
end
|
||||
else
|
||||
column_types = sti_class.decorate_columns(column_types)
|
||||
instance = sti_class.allocate.init_with('attributes' => record,
|
||||
'column_types' => column_types)
|
||||
end
|
||||
|
||||
instance
|
||||
sti_class = find_sti_class(record[inheritance_column])
|
||||
column_types = sti_class.decorate_columns(column_types)
|
||||
sti_class.allocate.init_with('attributes' => record, 'column_types' => column_types)
|
||||
end
|
||||
|
||||
# For internal use.
|
||||
|
|
|
@ -60,7 +60,6 @@ module ActiveRecord
|
|||
include AttributeMethods
|
||||
include Callbacks, ActiveModel::Observing, Timestamp
|
||||
include Associations
|
||||
include IdentityMap
|
||||
include ActiveModel::SecurePassword
|
||||
include AutosaveAssociation, NestedAttributes
|
||||
include Aggregations, Transactions, Reflection, Serialization, Store
|
||||
|
|
|
@ -115,10 +115,7 @@ module ActiveRecord
|
|||
# callbacks, Observer methods, or any <tt>:dependent</tt> association
|
||||
# options, use <tt>#destroy</tt>.
|
||||
def delete
|
||||
if persisted?
|
||||
self.class.delete(id)
|
||||
IdentityMap.remove(self) if IdentityMap.enabled?
|
||||
end
|
||||
self.class.delete(id) if persisted?
|
||||
@destroyed = true
|
||||
freeze
|
||||
end
|
||||
|
@ -129,7 +126,6 @@ module ActiveRecord
|
|||
destroy_associations
|
||||
|
||||
if persisted?
|
||||
IdentityMap.remove(self) if IdentityMap.enabled?
|
||||
pk = self.class.primary_key
|
||||
column = self.class.columns_hash[pk]
|
||||
substitute = connection.substitute_at(column, 0)
|
||||
|
@ -284,11 +280,9 @@ module ActiveRecord
|
|||
clear_aggregation_cache
|
||||
clear_association_cache
|
||||
|
||||
IdentityMap.without do
|
||||
fresh_object = self.class.unscoped { self.class.find(id, options) }
|
||||
@attributes.update(fresh_object.instance_variable_get('@attributes'))
|
||||
@columns_hash = fresh_object.instance_variable_get('@columns_hash')
|
||||
end
|
||||
fresh_object = self.class.unscoped { self.class.find(id, options) }
|
||||
@attributes.update(fresh_object.instance_variable_get('@attributes'))
|
||||
@columns_hash = fresh_object.instance_variable_get('@columns_hash')
|
||||
|
||||
@attributes_cache = {}
|
||||
self
|
||||
|
@ -363,10 +357,8 @@ module ActiveRecord
|
|||
attributes_values = arel_attributes_with_values_for_create(!id.nil?)
|
||||
|
||||
new_id = self.class.unscoped.insert attributes_values
|
||||
|
||||
self.id ||= new_id if self.class.primary_key
|
||||
|
||||
IdentityMap.add(self) if IdentityMap.enabled?
|
||||
@new_record = false
|
||||
id
|
||||
end
|
||||
|
|
|
@ -53,11 +53,6 @@ module ActiveRecord
|
|||
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
|
||||
end
|
||||
|
||||
initializer "active_record.identity_map" do |app|
|
||||
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
|
||||
"ActiveRecord::IdentityMap::Middleware" if config.active_record.delete(:identity_map)
|
||||
end
|
||||
|
||||
initializer "active_record.set_configs" do |app|
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
if app.config.active_record.delete(:whitelist_attributes)
|
||||
|
|
|
@ -168,13 +168,7 @@ module ActiveRecord
|
|||
default_scoped = with_default_scope
|
||||
|
||||
if default_scoped.equal?(self)
|
||||
@records = if @readonly_value.nil? && !@klass.locking_enabled?
|
||||
eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
|
||||
else
|
||||
IdentityMap.without do
|
||||
eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
|
||||
end
|
||||
end
|
||||
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
|
||||
|
||||
preload = @preload_values
|
||||
preload += @includes_values unless eager_loading?
|
||||
|
@ -274,7 +268,6 @@ module ActiveRecord
|
|||
# # The same idea applies to limit and order
|
||||
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
|
||||
def update_all(updates, conditions = nil, options = {})
|
||||
IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
|
||||
if conditions || options.present?
|
||||
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
|
||||
else
|
||||
|
@ -404,7 +397,6 @@ module ActiveRecord
|
|||
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
||||
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
||||
def delete_all(conditions = nil)
|
||||
IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
|
||||
if conditions
|
||||
where(conditions).delete_all
|
||||
else
|
||||
|
@ -437,7 +429,6 @@ module ActiveRecord
|
|||
# # Delete multiple rows
|
||||
# Todo.delete([2,3,4])
|
||||
def delete(id_or_array)
|
||||
IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
|
||||
where(primary_key => id_or_array).delete_all
|
||||
end
|
||||
|
||||
|
|
|
@ -318,17 +318,7 @@ module ActiveRecord
|
|||
def find_one(id)
|
||||
id = id.id if ActiveRecord::Base === id
|
||||
|
||||
if IdentityMap.enabled? && where_values.blank? &&
|
||||
limit_value.blank? && order_values.blank? &&
|
||||
includes_values.blank? && preload_values.blank? &&
|
||||
readonly_value.nil? && joins_values.blank? &&
|
||||
!@klass.locking_enabled? &&
|
||||
record = IdentityMap.get(@klass, id)
|
||||
return record
|
||||
end
|
||||
|
||||
column = columns_hash[primary_key]
|
||||
|
||||
substitute = connection.substitute_at(column, @bind_values.length)
|
||||
relation = where(table[primary_key].eq(substitute))
|
||||
relation.bind_values += [[column, id]]
|
||||
|
|
|
@ -7,20 +7,10 @@ module ActiveRecord
|
|||
#
|
||||
# Defines some test assertions to test against SQL queries.
|
||||
class TestCase < ActiveSupport::TestCase #:nodoc:
|
||||
setup :cleanup_identity_map
|
||||
|
||||
def setup
|
||||
cleanup_identity_map
|
||||
end
|
||||
|
||||
def teardown
|
||||
SQLCounter.log.clear
|
||||
end
|
||||
|
||||
def cleanup_identity_map
|
||||
ActiveRecord::IdentityMap.clear
|
||||
end
|
||||
|
||||
def assert_date_from_db(expected, actual, message = nil)
|
||||
# SybaseAdapter doesn't have a separate column type just for dates,
|
||||
# so the time is in the string and incorrectly formatted
|
||||
|
|
|
@ -251,7 +251,6 @@ module ActiveRecord
|
|||
remember_transaction_record_state
|
||||
yield
|
||||
rescue Exception
|
||||
IdentityMap.remove(self) if IdentityMap.enabled?
|
||||
restore_transaction_record_state
|
||||
raise
|
||||
ensure
|
||||
|
@ -270,7 +269,6 @@ module ActiveRecord
|
|||
def rolledback!(force_restore_state = false) #:nodoc:
|
||||
run_callbacks :rollback
|
||||
ensure
|
||||
IdentityMap.remove(self) if IdentityMap.enabled?
|
||||
restore_transaction_record_state(force_restore_state)
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
|
|||
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
|
||||
assert_nil post.tagging
|
||||
|
||||
ActiveRecord::IdentityMap.clear
|
||||
ActiveRecord::Base.store_full_sti_class = true
|
||||
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
|
||||
assert_instance_of Tagging, post.tagging
|
||||
|
|
|
@ -197,7 +197,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
author = authors(:david)
|
||||
post = author.post_about_thinking_with_last_comment
|
||||
last_comment = post.last_comment
|
||||
author = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
|
||||
author = assert_queries(3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
|
||||
assert_no_queries do
|
||||
assert_equal post, author.post_about_thinking_with_last_comment
|
||||
assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment
|
||||
|
@ -208,7 +208,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
post = posts(:welcome)
|
||||
author = post.author
|
||||
author_address = author.author_address
|
||||
post = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
|
||||
post = assert_queries(3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
|
||||
assert_no_queries do
|
||||
assert_equal author, post.author_with_address
|
||||
assert_equal author_address, post.author_with_address.author_address
|
||||
|
@ -611,9 +611,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert posts[1].categories.include?(categories(:general))
|
||||
end
|
||||
|
||||
# This is only really relevant when the identity map is off. Since the preloader for habtm
|
||||
# gets raw row hashes from the database and then instantiates them, this test ensures that
|
||||
# it only instantiates one actual object per record from the database.
|
||||
# Since the preloader for habtm gets raw row hashes from the database and then
|
||||
# instantiates them, this test ensures that it only instantiates one actual
|
||||
# object per record from the database.
|
||||
def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times
|
||||
welcome = posts(:welcome)
|
||||
categories = Category.includes(:posts)
|
||||
|
@ -1002,18 +1002,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
|
||||
posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
|
||||
end
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
|
@ -1027,7 +1027,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
|
@ -1116,7 +1116,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
|||
def test_preloading_empty_belongs_to_polymorphic
|
||||
t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
|
||||
|
||||
tagging = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Tagging.preload(:taggable).find(t.id) }
|
||||
tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) }
|
||||
assert_no_queries { assert_nil tagging.taggable }
|
||||
end
|
||||
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
require "cases/helper"
|
||||
require 'models/author'
|
||||
require 'models/post'
|
||||
|
||||
if ActiveRecord::IdentityMap.enabled?
|
||||
class InverseHasManyIdentityMapTest < ActiveRecord::TestCase
|
||||
fixtures :authors, :posts
|
||||
|
||||
def test_parent_instance_should_be_shared_with_every_child_on_find
|
||||
m = Author.first
|
||||
is = m.posts
|
||||
is.each do |i|
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
|
||||
end
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_eager_loaded_children
|
||||
m = Author.find(:first, :include => :posts)
|
||||
is = m.posts
|
||||
is.each do |i|
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
|
||||
end
|
||||
|
||||
m = Author.find(:first, :include => :posts, :order => 'posts.id')
|
||||
is = m.posts
|
||||
is.each do |i|
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
|
||||
end
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_newly_built_child
|
||||
m = Author.first
|
||||
i = m.posts.build(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
|
||||
m = Author.first
|
||||
i = m.posts.build {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
|
||||
assert_not_nil i.title, "Child attributes supplied to build via blocks should be populated"
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_newly_created_child
|
||||
m = Author.first
|
||||
i = m.posts.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
|
||||
m = Author.first
|
||||
i = m.posts.create!(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_newly_block_style_created_child
|
||||
m = Author.first
|
||||
i = m.posts.create {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
|
||||
assert_not_nil i.title, "Child attributes supplied to create via blocks should be populated"
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_poked_in_child
|
||||
m = Author.first
|
||||
i = Post.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
m.posts << i
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
|
||||
m = Author.first
|
||||
i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
m.posts = [i]
|
||||
assert_same m, i.author
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
|
||||
end
|
||||
|
||||
def test_parent_instance_should_be_shared_with_replaced_via_method_children
|
||||
m = Author.first
|
||||
i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
|
||||
m.posts = [i]
|
||||
assert_not_nil i.author
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
|
||||
m.name = 'Bongo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
|
||||
i.author.name = 'Mungo'
|
||||
assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -978,10 +978,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
|
|||
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
|
||||
# Oracle saves empty string as NULL
|
||||
if current_adapter?(:OracleAdapter)
|
||||
expected = ActiveRecord::IdentityMap.enabled? ?
|
||||
[nil, nil, '', ''] :
|
||||
[nil, nil, nil, nil]
|
||||
assert_equal expected, values
|
||||
assert_equal [nil, nil, nil, nil], values
|
||||
else
|
||||
assert_equal ['', '', '', ''], values
|
||||
end
|
||||
|
@ -1077,8 +1074,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
|
|||
@ship.save(:validate => false)
|
||||
# Oracle saves empty string as NULL
|
||||
if current_adapter?(:OracleAdapter)
|
||||
expected = ActiveRecord::IdentityMap.enabled? ? [nil, ''] : [nil, nil]
|
||||
assert_equal expected, [@ship.reload.name, @ship.pirate.catchphrase]
|
||||
assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
|
||||
else
|
||||
assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
|
||||
end
|
||||
|
|
|
@ -1139,7 +1139,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert g.save
|
||||
|
||||
# Reload and check that we have all the geometric attributes.
|
||||
h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
|
||||
h = Geometric.find(g.id)
|
||||
|
||||
assert_equal '(5,6.1)', h.a_point
|
||||
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
|
||||
|
@ -1168,7 +1168,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert g.save
|
||||
|
||||
# Reload and check that we have all the geometric attributes.
|
||||
h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
|
||||
h = Geometric.find(g.id)
|
||||
|
||||
assert_equal '(5,6.1)', h.a_point
|
||||
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
|
||||
|
|
|
@ -19,9 +19,6 @@ require 'support/connection'
|
|||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
# Enable Identity Map only when ENV['IM'] is set to "true"
|
||||
ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true")
|
||||
|
||||
# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true
|
||||
ActiveRecord::Base.dependent_restrict_raises = false
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
require "cases/helper"
|
||||
require "rack"
|
||||
|
||||
module ActiveRecord
|
||||
module IdentityMap
|
||||
class MiddlewareTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
super
|
||||
@enabled = IdentityMap.enabled
|
||||
IdentityMap.enabled = false
|
||||
end
|
||||
|
||||
def teardown
|
||||
super
|
||||
IdentityMap.enabled = @enabled
|
||||
IdentityMap.clear
|
||||
end
|
||||
|
||||
def test_delegates
|
||||
called = false
|
||||
mw = Middleware.new lambda { |env|
|
||||
called = true
|
||||
[200, {}, nil]
|
||||
}
|
||||
mw.call({})
|
||||
assert called, 'middleware delegated'
|
||||
end
|
||||
|
||||
def test_im_enabled_during_delegation
|
||||
mw = Middleware.new lambda { |env|
|
||||
assert IdentityMap.enabled?, 'identity map should be enabled'
|
||||
[200, {}, nil]
|
||||
}
|
||||
mw.call({})
|
||||
end
|
||||
|
||||
class Enum < Struct.new(:iter)
|
||||
def each(&b)
|
||||
iter.call(&b)
|
||||
end
|
||||
end
|
||||
|
||||
def test_im_enabled_during_body_each
|
||||
mw = Middleware.new lambda { |env|
|
||||
[200, {}, Enum.new(lambda { |&b|
|
||||
assert IdentityMap.enabled?, 'identity map should be enabled'
|
||||
b.call "hello"
|
||||
})]
|
||||
}
|
||||
body = mw.call({}).last
|
||||
body.each { |x| assert_equal 'hello', x }
|
||||
end
|
||||
|
||||
def test_im_disabled_after_body_close
|
||||
mw = Middleware.new lambda { |env| [200, {}, []] }
|
||||
body = mw.call({}).last
|
||||
assert IdentityMap.enabled?, 'identity map should be enabled'
|
||||
body.close
|
||||
assert !IdentityMap.enabled?, 'identity map should be disabled'
|
||||
end
|
||||
|
||||
def test_im_cleared_after_body_close
|
||||
mw = Middleware.new lambda { |env| [200, {}, []] }
|
||||
body = mw.call({}).last
|
||||
|
||||
IdentityMap.repository['hello'] = 'world'
|
||||
assert !IdentityMap.repository.empty?, 'repo should not be empty'
|
||||
|
||||
body.close
|
||||
assert IdentityMap.repository.empty?, 'repo should be empty'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,439 +0,0 @@
|
|||
require "cases/helper"
|
||||
|
||||
require 'models/developer'
|
||||
require 'models/project'
|
||||
require 'models/company'
|
||||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
require 'models/computer'
|
||||
require 'models/customer'
|
||||
require 'models/order'
|
||||
require 'models/post'
|
||||
require 'models/author'
|
||||
require 'models/tag'
|
||||
require 'models/tagging'
|
||||
require 'models/comment'
|
||||
require 'models/sponsor'
|
||||
require 'models/member'
|
||||
require 'models/essay'
|
||||
require 'models/subscriber'
|
||||
require "models/pirate"
|
||||
require "models/bird"
|
||||
require "models/parrot"
|
||||
|
||||
if ActiveRecord::IdentityMap.enabled?
|
||||
class IdentityMapTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :companies, :developers, :projects, :topics,
|
||||
:developers_projects, :computers, :authors, :author_addresses,
|
||||
:posts, :tags, :taggings, :comments, :subscribers
|
||||
|
||||
##############################################################################
|
||||
# Basic tests checking if IM is functioning properly on basic find operations#
|
||||
##############################################################################
|
||||
|
||||
def test_find_id
|
||||
assert_same(Client.find(3), Client.find(3))
|
||||
end
|
||||
|
||||
def test_find_id_without_identity_map
|
||||
ActiveRecord::IdentityMap.without do
|
||||
assert_not_same(Client.find(3), Client.find(3))
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_id_use_identity_map
|
||||
ActiveRecord::IdentityMap.enabled = false
|
||||
ActiveRecord::IdentityMap.use do
|
||||
assert_same(Client.find(3), Client.find(3))
|
||||
end
|
||||
ActiveRecord::IdentityMap.enabled = true
|
||||
end
|
||||
|
||||
def test_find_pkey
|
||||
assert_same(
|
||||
Subscriber.find('swistak'),
|
||||
Subscriber.find('swistak')
|
||||
)
|
||||
end
|
||||
|
||||
def test_find_by_id
|
||||
assert_same(
|
||||
Client.find_by_id(3),
|
||||
Client.find_by_id(3)
|
||||
)
|
||||
end
|
||||
|
||||
def test_find_by_string_and_numeric_id
|
||||
assert_same(
|
||||
Client.find_by_id("3"),
|
||||
Client.find_by_id(3)
|
||||
)
|
||||
end
|
||||
|
||||
def test_find_by_pkey
|
||||
assert_same(
|
||||
Subscriber.find_by_nick('swistak'),
|
||||
Subscriber.find_by_nick('swistak')
|
||||
)
|
||||
end
|
||||
|
||||
def test_find_first_id
|
||||
assert_same(
|
||||
Client.find(:first, :conditions => {:id => 1}),
|
||||
Client.find(:first, :conditions => {:id => 1})
|
||||
)
|
||||
end
|
||||
|
||||
def test_find_first_pkey
|
||||
assert_same(
|
||||
Subscriber.find(:first, :conditions => {:nick => 'swistak'}),
|
||||
Subscriber.find(:first, :conditions => {:nick => 'swistak'})
|
||||
)
|
||||
end
|
||||
|
||||
def test_queries_are_not_executed_when_finding_by_id
|
||||
Post.find 1
|
||||
assert_no_queries do
|
||||
Post.find 1
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Tests checking if IM is functioning properly on more advanced finds #
|
||||
# and associations #
|
||||
##############################################################################
|
||||
|
||||
def test_owner_object_is_associated_from_identity_map
|
||||
post = Post.find(1)
|
||||
comment = post.comments.first
|
||||
|
||||
assert_no_queries do
|
||||
comment.post
|
||||
end
|
||||
assert_same post, comment.post
|
||||
end
|
||||
|
||||
def test_associated_object_are_assigned_from_identity_map
|
||||
post = Post.find(1)
|
||||
|
||||
post.comments.each do |comment|
|
||||
assert_same post, comment.post
|
||||
assert_equal post.object_id, comment.post.object_id
|
||||
end
|
||||
end
|
||||
|
||||
def test_creation
|
||||
t1 = Topic.create("title" => "t1")
|
||||
t2 = Topic.find(t1.id)
|
||||
assert_same(t1, t2)
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Tests checking if IM is functioning properly on classes with multiple #
|
||||
# types of inheritance #
|
||||
##############################################################################
|
||||
|
||||
def test_inherited_without_type_attribute_without_identity_map
|
||||
ActiveRecord::IdentityMap.without do
|
||||
p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
|
||||
p2 = Pirate.find(p1.id)
|
||||
assert_not_same(p1, p2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_inherited_with_type_attribute_without_identity_map
|
||||
ActiveRecord::IdentityMap.without do
|
||||
c = comments(:sub_special_comment)
|
||||
c1 = SubSpecialComment.find(c.id)
|
||||
c2 = Comment.find(c.id)
|
||||
assert_same(c1.class, c2.class)
|
||||
end
|
||||
end
|
||||
|
||||
def test_inherited_without_type_attribute
|
||||
p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
|
||||
p2 = Pirate.find(p1.id)
|
||||
assert_not_same(p1, p2)
|
||||
end
|
||||
|
||||
def test_inherited_with_type_attribute
|
||||
c = comments(:sub_special_comment)
|
||||
c1 = SubSpecialComment.find(c.id)
|
||||
c2 = Comment.find(c.id)
|
||||
assert_same(c1, c2)
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Tests checking dirty attribute behavior with IM #
|
||||
##############################################################################
|
||||
|
||||
def test_loading_new_instance_should_not_update_dirty_attributes
|
||||
swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
|
||||
swistak.name = "Swistak Sreberkowiec"
|
||||
assert_equal(["name"], swistak.changed)
|
||||
assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
|
||||
|
||||
assert swistak.name_changed?
|
||||
assert_equal("Swistak Sreberkowiec", swistak.name)
|
||||
end
|
||||
|
||||
def test_loading_new_instance_should_change_dirty_attribute_original_value
|
||||
swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
|
||||
swistak.name = "Swistak Sreberkowiec"
|
||||
|
||||
Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"})
|
||||
|
||||
assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
|
||||
assert_equal("Swistak Sreberkowiec", swistak.name)
|
||||
end
|
||||
|
||||
def test_loading_new_instance_should_remove_dirt
|
||||
swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
|
||||
swistak.name = "Swistak Sreberkowiec"
|
||||
|
||||
assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
|
||||
|
||||
Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"})
|
||||
|
||||
assert_equal("Swistak Sreberkowiec", swistak.name)
|
||||
assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
|
||||
assert swistak.name_changed?
|
||||
end
|
||||
|
||||
def test_has_many_associations
|
||||
pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
||||
pirate.birds.create!(:name => 'Posideons Killer')
|
||||
pirate.birds.create!(:name => 'Killer bandita Dionne')
|
||||
|
||||
posideons, _ = pirate.birds
|
||||
|
||||
pirate.reload
|
||||
|
||||
pirate.birds_attributes = [{ :id => posideons.id, :name => 'Grace OMalley' }]
|
||||
assert_equal 'Grace OMalley', pirate.birds.to_a.find { |r| r.id == posideons.id }.name
|
||||
end
|
||||
|
||||
def test_changing_associations
|
||||
post1 = Post.create("title" => "One post", "body" => "Posting...")
|
||||
post2 = Post.create("title" => "Another post", "body" => "Posting... Again...")
|
||||
comment = Comment.new("body" => "comment")
|
||||
|
||||
comment.post = post1
|
||||
assert comment.save
|
||||
|
||||
assert_same(post1.comments.first, comment)
|
||||
|
||||
comment.post = post2
|
||||
assert comment.save
|
||||
|
||||
assert_same(post2.comments.first, comment)
|
||||
assert_equal(0, post1.comments.size)
|
||||
end
|
||||
|
||||
def test_im_with_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
|
||||
tag = posts(:welcome).tags.first
|
||||
tag_with_joins_and_select = posts(:welcome).tags.add_joins_and_select.first
|
||||
assert_same(tag, tag_with_joins_and_select)
|
||||
assert_nothing_raised(NoMethodError, "Joins/select was not loaded") { tag.author_id }
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Tests checking Identity Map behavior with preloaded associations, joins, #
|
||||
# includes etc. #
|
||||
##############################################################################
|
||||
|
||||
def test_find_with_preloaded_associations
|
||||
assert_queries(2) do
|
||||
posts = Post.preload(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
# With IM we'll retrieve post object from previous query, it'll have comments
|
||||
# already preloaded from first call
|
||||
assert_queries(1) do
|
||||
posts = Post.preload(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
assert_queries(2) do
|
||||
posts = Post.preload(:author).order('posts.id')
|
||||
assert posts.first.author
|
||||
end
|
||||
|
||||
# With IM we'll retrieve post object from previous query, it'll have comments
|
||||
# already preloaded from first call
|
||||
assert_queries(1) do
|
||||
posts = Post.preload(:author).order('posts.id')
|
||||
assert posts.first.author
|
||||
end
|
||||
|
||||
assert_queries(1) do
|
||||
posts = Post.preload(:author, :comments).order('posts.id')
|
||||
assert posts.first.author
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_with_included_associations
|
||||
assert_queries(2) do
|
||||
posts = Post.includes(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
assert_queries(1) do
|
||||
posts = Post.scoped.includes(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
assert_queries(2) do
|
||||
posts = Post.includes(:author).order('posts.id')
|
||||
assert posts.first.author
|
||||
end
|
||||
|
||||
assert_queries(1) do
|
||||
posts = Post.includes(:author, :comments).order('posts.id')
|
||||
assert posts.first.author
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_loading_with_conditions_on_joined_table_preloads
|
||||
posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
assert_same posts.first.author, Author.first
|
||||
|
||||
posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
assert_same posts.first.author, Author.first
|
||||
|
||||
posts = Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
assert_same posts.first.author, Author.first
|
||||
|
||||
posts = Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
|
||||
assert_equal posts(:welcome, :thinking), posts
|
||||
assert_same posts.first.author, Author.first
|
||||
end
|
||||
|
||||
def test_eager_loading_with_conditions_on_string_joined_table_preloads
|
||||
posts = assert_queries(2) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
|
||||
posts = assert_queries(1) do
|
||||
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
|
||||
end
|
||||
assert_equal [posts(:welcome)], posts
|
||||
assert_equal authors(:david), assert_no_queries { posts[0].author}
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Behaviour related to saving failures
|
||||
##############################################################################
|
||||
|
||||
def test_reload_object_if_save_failed
|
||||
developer = Developer.first
|
||||
developer.salary = 0
|
||||
|
||||
assert !developer.save
|
||||
|
||||
same_developer = Developer.first
|
||||
|
||||
assert_not_same developer, same_developer
|
||||
assert_not_equal 0, same_developer.salary
|
||||
assert_not_equal developer.salary, same_developer.salary
|
||||
end
|
||||
|
||||
def test_reload_object_if_forced_save_failed
|
||||
developer = Developer.first
|
||||
developer.salary = 0
|
||||
|
||||
assert_raise(ActiveRecord::RecordInvalid) { developer.save! }
|
||||
|
||||
same_developer = Developer.first
|
||||
|
||||
assert_not_same developer, same_developer
|
||||
assert_not_equal 0, same_developer.salary
|
||||
assert_not_equal developer.salary, same_developer.salary
|
||||
end
|
||||
|
||||
def test_reload_object_if_update_attributes_fails
|
||||
developer = Developer.first
|
||||
developer.salary = 0
|
||||
|
||||
assert !developer.update_attributes(:salary => 0)
|
||||
|
||||
same_developer = Developer.first
|
||||
|
||||
assert_not_same developer, same_developer
|
||||
assert_not_equal 0, same_developer.salary
|
||||
assert_not_equal developer.salary, same_developer.salary
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Behaviour of readonly, frozen, destroyed
|
||||
##############################################################################
|
||||
|
||||
def test_find_using_identity_map_respects_readonly_when_loading_associated_object_first
|
||||
author = Author.first
|
||||
readonly_comment = author.readonly_comments.first
|
||||
|
||||
comment = Comment.first
|
||||
assert !comment.readonly?
|
||||
|
||||
assert readonly_comment.readonly?
|
||||
|
||||
assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save}
|
||||
assert comment.save
|
||||
end
|
||||
|
||||
def test_find_using_identity_map_respects_readonly
|
||||
comment = Comment.first
|
||||
assert !comment.readonly?
|
||||
|
||||
author = Author.first
|
||||
readonly_comment = author.readonly_comments.first
|
||||
|
||||
assert readonly_comment.readonly?
|
||||
|
||||
assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save}
|
||||
assert comment.save
|
||||
end
|
||||
|
||||
def test_find_using_select_and_identity_map
|
||||
author_id, author = Author.select('id').first, Author.first
|
||||
|
||||
assert_equal author_id, author
|
||||
assert_same author_id, author
|
||||
assert_not_nil author.name
|
||||
|
||||
post, post_id = Post.first, Post.select('id').first
|
||||
|
||||
assert_equal post_id, post
|
||||
assert_same post_id, post
|
||||
assert_not_nil post.title
|
||||
end
|
||||
|
||||
# Currently AR is not allowing changing primary key (see Persistence#update)
|
||||
# So we ignore it. If this changes, this test needs to be uncommented.
|
||||
# def test_updating_of_pkey
|
||||
# assert client = Client.find(3),
|
||||
# client.update_attribute(:id, 666)
|
||||
#
|
||||
# assert Client.find(666)
|
||||
# assert_same(client, Client.find(666))
|
||||
#
|
||||
# s = Subscriber.find_by_nick('swistak')
|
||||
# assert s.update_attribute(:nick, 'swistakTheJester')
|
||||
# assert_equal('swistakTheJester', s.nick)
|
||||
#
|
||||
# assert stj = Subscriber.find_by_nick('swistakTheJester')
|
||||
# assert_same(s, stj)
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
|
@ -11,8 +11,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
|
|||
|
||||
def setup
|
||||
@old_logger = ActiveRecord::Base.logger
|
||||
@using_identity_map = ActiveRecord::IdentityMap.enabled?
|
||||
ActiveRecord::IdentityMap.enabled = false
|
||||
Developer.primary_key
|
||||
super
|
||||
ActiveRecord::LogSubscriber.attach_to(:active_record)
|
||||
|
@ -22,7 +20,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
|
|||
super
|
||||
ActiveRecord::LogSubscriber.log_subscribers.pop
|
||||
ActiveRecord::Base.logger = @old_logger
|
||||
ActiveRecord::IdentityMap.enabled = @using_identity_map
|
||||
end
|
||||
|
||||
def set_logger(logger)
|
||||
|
@ -103,13 +100,4 @@ class LogSubscriberTest < ActiveRecord::TestCase
|
|||
def test_initializes_runtime
|
||||
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
|
||||
end
|
||||
|
||||
def test_log
|
||||
ActiveRecord::IdentityMap.use do
|
||||
Post.find 1
|
||||
Post.find 1
|
||||
end
|
||||
wait
|
||||
assert_match(/From Identity Map/, @logger.logged(:debug).last)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -107,7 +107,7 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_find_queries
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
|
||||
assert_queries(2) { Task.find(1); Task.find(1) }
|
||||
end
|
||||
|
||||
def test_find_queries_with_cache
|
||||
|
|
|
@ -323,7 +323,7 @@ class RelationTest < ActiveRecord::TestCase
|
|||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
assert_queries(2) do
|
||||
posts = Post.preload(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
@ -333,12 +333,12 @@ class RelationTest < ActiveRecord::TestCase
|
|||
assert posts.first.author
|
||||
end
|
||||
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
assert_queries(2) do
|
||||
posts = Post.preload(:author).order('posts.id')
|
||||
assert posts.first.author
|
||||
end
|
||||
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
|
||||
assert_queries(3) do
|
||||
posts = Post.preload(:author, :comments).order('posts.id')
|
||||
assert posts.first.author
|
||||
assert posts.first.comments.first
|
||||
|
@ -351,7 +351,7 @@ class RelationTest < ActiveRecord::TestCase
|
|||
assert posts.first.comments.first
|
||||
end
|
||||
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
|
||||
assert_queries(2) do
|
||||
posts = Post.scoped.includes(:comments).order('posts.id')
|
||||
assert posts.first.comments.first
|
||||
end
|
||||
|
@ -361,7 +361,7 @@ class RelationTest < ActiveRecord::TestCase
|
|||
assert posts.first.author
|
||||
end
|
||||
|
||||
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
|
||||
assert_queries(3) do
|
||||
posts = Post.includes(:author, :comments).order('posts.id')
|
||||
assert posts.first.author
|
||||
assert posts.first.comments.first
|
||||
|
@ -685,10 +685,8 @@ class RelationTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_relation_merging_with_preload
|
||||
ActiveRecord::IdentityMap.without do
|
||||
[Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts|
|
||||
assert_queries(2) { assert posts.first.author }
|
||||
end
|
||||
[Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts|
|
||||
assert_queries(2) { assert posts.first.author }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module ARTest
|
|||
end
|
||||
|
||||
def self.connect
|
||||
puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}"
|
||||
puts "Using #{connection_name}"
|
||||
ActiveRecord::Model.logger = ActiveSupport::Logger.new("debug.log")
|
||||
ActiveRecord::Model.configurations = connection_config
|
||||
ActiveRecord::Model.establish_connection 'arunit'
|
||||
|
|
|
@ -34,7 +34,6 @@ class Build
|
|||
self.options.update(options)
|
||||
Dir.chdir(dir) do
|
||||
announce(heading)
|
||||
ENV['IM'] = identity_map?.inspect
|
||||
rake(*tasks)
|
||||
end
|
||||
end
|
||||
|
@ -45,7 +44,7 @@ class Build
|
|||
|
||||
def heading
|
||||
heading = [gem]
|
||||
heading << "with #{adapter} IM #{identity_map? ? 'enabled' : 'disabled'}" if activerecord?
|
||||
heading << "with #{adapter}" if activerecord?
|
||||
heading << "in isolation" if isolated?
|
||||
heading.join(' ')
|
||||
end
|
||||
|
@ -61,7 +60,6 @@ class Build
|
|||
def key
|
||||
key = [gem]
|
||||
key << adapter if activerecord?
|
||||
key << 'IM' if identity_map?
|
||||
key << 'isolated' if isolated?
|
||||
key.join(':')
|
||||
end
|
||||
|
@ -70,10 +68,6 @@ class Build
|
|||
gem == 'activerecord'
|
||||
end
|
||||
|
||||
def identity_map?
|
||||
options[:identity_map]
|
||||
end
|
||||
|
||||
def isolated?
|
||||
options[:isolated]
|
||||
end
|
||||
|
@ -107,7 +101,6 @@ ENV['GEM'].split(',').each do |gem|
|
|||
results[build.key] = build.run!
|
||||
|
||||
if build.activerecord?
|
||||
build.options[:identity_map] = true
|
||||
results[build.key] = build.run!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -288,8 +288,6 @@ h4. Configuring Active Record
|
|||
|
||||
* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app.
|
||||
|
||||
* +config.active_record.identity_map+ controls whether the identity map is enabled, and is false by default.
|
||||
|
||||
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
|
||||
|
||||
* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
|
||||
|
|
|
@ -18,10 +18,6 @@ if defined?(ActiveRecord::Base)
|
|||
class ActiveSupport::TestCase
|
||||
include ActiveRecord::TestFixtures
|
||||
self.fixture_path = "#{Rails.root}/test/fixtures/"
|
||||
|
||||
setup do
|
||||
ActiveRecord::IdentityMap.clear
|
||||
end
|
||||
end
|
||||
|
||||
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
|
||||
|
|
|
@ -115,7 +115,6 @@ module ApplicationTests
|
|||
boot!
|
||||
assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
|
||||
assert !middleware.include?("ActiveRecord::QueryCache")
|
||||
assert !middleware.include?("ActiveRecord::IdentityMap::Middleware")
|
||||
end
|
||||
|
||||
test "removes lock if allow concurrency is set" do
|
||||
|
@ -173,12 +172,6 @@ module ApplicationTests
|
|||
assert_equal "Rack::Runtime", middleware.fourth
|
||||
end
|
||||
|
||||
test "identity map is inserted" do
|
||||
add_to_config "config.active_record.identity_map = true"
|
||||
boot!
|
||||
assert middleware.include?("ActiveRecord::IdentityMap::Middleware")
|
||||
end
|
||||
|
||||
test "insert middleware before" do
|
||||
add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config"
|
||||
boot!
|
||||
|
|
Loading…
Reference in New Issue