提交 00075016 编写于 作者: E Eugene Kenny

Only define attribute methods from schema cache

To define the attribute methods for a model, Active Record needs to know
the schema of the underlying table, which is usually achieved by making
a request to the database. This is undesirable behaviour while the app
is booting, for two reasons: it makes the boot process dependent on the
availability of the database, and it means every new process will make
one query for each table, which can cause issues for large applications.

However, if the application is using the schema cache dump feature, then
the schema cache already contains the necessary information, and we can
define the attribute methods without causing any extra database queries.
上级 b658743a
...@@ -77,6 +77,11 @@ def columns_hash(table_name) ...@@ -77,6 +77,11 @@ def columns_hash(table_name)
}] }]
end end
# Checks whether the columns hash is already cached for a table.
def columns_hash?(table_name)
@columns_hash.key?(table_name)
end
# Clears out internal caches # Clears out internal caches
def clear! def clear!
@columns.clear @columns.clear
......
...@@ -283,6 +283,10 @@ def type_caster # :nodoc: ...@@ -283,6 +283,10 @@ def type_caster # :nodoc:
TypeCaster::Map.new(self) TypeCaster::Map.new(self)
end end
def _internal? # :nodoc:
false
end
private private
def cached_find_by_statement(key, &block) def cached_find_by_statement(key, &block)
......
...@@ -8,6 +8,10 @@ module ActiveRecord ...@@ -8,6 +8,10 @@ module ActiveRecord
# as which environment migrations were run in. # as which environment migrations were run in.
class InternalMetadata < ActiveRecord::Base # :nodoc: class InternalMetadata < ActiveRecord::Base # :nodoc:
class << self class << self
def _internal?
true
end
def primary_key def primary_key
"key" "key"
end end
......
...@@ -140,7 +140,19 @@ class Railtie < Rails::Railtie # :nodoc: ...@@ -140,7 +140,19 @@ class Railtie < Rails::Railtie # :nodoc:
initializer "active_record.define_attribute_methods" do |app| initializer "active_record.define_attribute_methods" do |app|
config.after_initialize do config.after_initialize do
ActiveSupport.on_load(:active_record) do ActiveSupport.on_load(:active_record) do
descendants.each(&:define_attribute_methods) if app.config.eager_load if app.config.eager_load
descendants.each do |model|
# SchemaMigration and InternalMetadata both override `table_exists?`
# to bypass the schema cache, so skip them to avoid the extra queries.
next if model._internal?
# If there's no connection yet, or the schema cache doesn't have the columns
# hash for the model cached, `define_attribute_methods` would trigger a query.
next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
model.define_attribute_methods
end
end
end end
end end
end end
......
...@@ -10,6 +10,10 @@ module ActiveRecord ...@@ -10,6 +10,10 @@ module ActiveRecord
# to be executed the next time. # to be executed the next time.
class SchemaMigration < ActiveRecord::Base # :nodoc: class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self class << self
def _internal?
true
end
def primary_key def primary_key
"version" "version"
end end
......
...@@ -91,6 +91,22 @@ def test_clear_data_source_cache ...@@ -91,6 +91,22 @@ def test_clear_data_source_cache
@cache.clear_data_source_cache!("posts") @cache.clear_data_source_cache!("posts")
end end
test "#columns_hash? is populated by #columns_hash" do
assert_not @cache.columns_hash?("posts")
@cache.columns_hash("posts")
assert @cache.columns_hash?("posts")
end
test "#columns_hash? is not populated by #data_source_exists?" do
assert_not @cache.columns_hash?("posts")
@cache.data_source_exists?("posts")
assert_not @cache.columns_hash?("posts")
end
private private
def schema_dump_path def schema_dump_path
......
...@@ -331,7 +331,7 @@ class Post < ActiveRecord::Base ...@@ -331,7 +331,7 @@ class Post < ActiveRecord::Base
assert_not_includes Post.instance_methods, :title assert_not_includes Post.instance_methods, :title
end end
test "eager loads attribute methods in production" do test "does not eager load attribute methods in production when the schema cache is empty" do
app_file "app/models/post.rb", <<-RUBY app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base class Post < ActiveRecord::Base
end end
...@@ -354,9 +354,71 @@ class Post < ActiveRecord::Base ...@@ -354,9 +354,71 @@ class Post < ActiveRecord::Base
app "production" app "production"
assert_not_includes Post.instance_methods, :title
end
test "eager loads attribute methods in production when the schema cache is populated" do
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app_file "config/initializers/active_record.rb", <<-RUBY
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define(version: 1) do
create_table :posts do |t|
t.string :title
end
end
RUBY
add_to_config <<-RUBY
config.eager_load = true
config.cache_classes = true
RUBY
app_file "config/initializers/schema_cache.rb", <<-RUBY
ActiveRecord::Base.connection.schema_cache.add("posts")
RUBY
app "production"
assert_includes Post.instance_methods, :title assert_includes Post.instance_methods, :title
end end
test "does not attempt to eager load attribute methods for models that aren't connected" do
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app_file "config/initializers/active_record.rb", <<-RUBY
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define(version: 1) do
create_table :posts do |t|
t.string :title
end
end
RUBY
add_to_config <<-RUBY
config.eager_load = true
config.cache_classes = true
RUBY
app_file "app/models/comment.rb", <<-RUBY
class Comment < ActiveRecord::Base
establish_connection(adapter: "mysql2", database: "does_not_exist")
end
RUBY
assert_nothing_raised do
app "production"
end
end
test "initialize an eager loaded, cache classes app" do test "initialize an eager loaded, cache classes app" do
add_to_config <<-RUBY add_to_config <<-RUBY
config.eager_load = true config.eager_load = true
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册