1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/lib/active_record/railtie.rb

267 lines
9.7 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require "active_record"
require "rails"
require "active_support/core_ext/object/try"
require "active_model/railtie"
# For now, action_controller must always be present with
2016-07-12 02:51:36 -04:00
# Rails, so let's make sure that it gets required before
# here. This is needed for correctly setting up the middleware.
# In the future, this might become an optional require.
require "action_controller/railtie"
module ActiveRecord
2010-06-16 13:45:04 -04:00
# = Active Record Railtie
2012-09-22 00:38:18 -04:00
class Railtie < Rails::Railtie # :nodoc:
config.active_record = ActiveSupport::OrderedOptions.new
2016-08-06 13:37:57 -04:00
config.app_generators.orm :active_record, migration: true,
timestamps: true
config.action_dispatch.rescue_responses.merge!(
"ActiveRecord::RecordNotFound" => :not_found,
"ActiveRecord::StaleObjectError" => :conflict,
"ActiveRecord::RecordInvalid" => :unprocessable_entity,
"ActiveRecord::RecordNotSaved" => :unprocessable_entity
)
config.active_record.use_schema_cache_dump = true
config.active_record.maintain_test_schema = true
config.active_record.has_many_inversing = false
config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new
config.active_record.sqlite3.represent_boolean_as_integer = nil
config.eager_load_namespaces << ActiveRecord
rake_tasks do
namespace :db do
task :load_config do
Ensure Active Record connection consistency Currently Active Record can be configured via the environment variable `DATABASE_URL` or by manually injecting a hash of values which is what Rails does, reading in `database.yml` and setting Active Record appropriately. Active Record expects to be able to use `DATABASE_URL` without the use of Rails, and we cannot rip out this functionality without deprecating. This presents a problem though when both config is set, and a `DATABASE_URL` is present. Currently the `DATABASE_URL` should "win" and none of the values in `database.yml` are used. This is somewhat unexpected to me if I were to set values such as `pool` in the `production:` group of `database.yml` they are ignored. There are many ways that active record initiates a connection today: - Stand Alone (without rails) - `rake db:<tasks>` - ActiveRecord.establish_connection - With Rails - `rake db:<tasks>` - `rails <server> | <console>` - `rails dbconsole` We should make all of these behave exactly the same way. The best way to do this is to put all of this logic in one place so it is guaranteed to be used. Here is my prosed matrix of how this behavior should work: ``` No database.yml No DATABASE_URL => Error ``` ``` database.yml present No DATABASE_URL => Use database.yml configuration ``` ``` No database.yml DATABASE_URL present => use DATABASE_URL configuration ``` ``` database.yml present DATABASE_URL present => Merged into `url` sub key. If both specify `url` sub key, the `database.yml` `url` sub key "wins". If other paramaters `adapter` or `database` are specified in YAML, they are discarded as the `url` sub key "wins". ``` ### Implementation Current implementation uses `ActiveRecord::Base.configurations` to resolve and merge all connection information before returning. This is achieved through a utility class: `ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig`. To understand the exact behavior of this class, it is best to review the behavior in activerecord/test/cases/connection_adapters/connection_handler_test.rb though it should match the above proposal.
2014-01-01 17:33:59 -05:00
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
2016-01-20 18:11:58 -05:00
if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT)
if engine.paths["db/migrate"].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a
end
end
end
end
load "active_record/railties/databases.rake"
end
# When loading console, force ActiveRecord::Base to be loaded
# to avoid cross references when loading a constant for the
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
require "active_record/base"
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
ActiveRecord::Base.verbose_query_logs = false
end
2013-03-14 01:17:13 -04:00
runner do
require "active_record/base"
end
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
end
end
initializer "active_record.logger" do
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
initializer "active_record.backtrace_cleaner" do
ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner }
end
initializer "active_record.migration_error" do |app|
if config.active_record.delete(:migration_error) == :page_load
2015-08-08 16:15:28 -04:00
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
ActiveRecord::Migration::CheckPending,
file_watcher: app.config.file_watcher
end
end
Adds basic automatic database switching to Rails The following PR adds behavior to Rails to allow an application to automatically switch it's connection from the primary to the replica. A request will be sent to the replica if: * The request is a read request (`GET` or `HEAD`) * AND It's been 2 seconds since the last write to the database (because we don't want to send a user to a replica if the write hasn't made it to the replica yet) A request will be sent to the primary if: * It's not a GET/HEAD request (ie is a POST, PATCH, etc) * Has been less than 2 seconds since the last write to the database The implementation that decides when to switch reads (the 2 seconds) is "safe" to use in production but not recommended without adequate testing with your infrastructure. At GitHub in addition to the a 5 second delay we have a curcuit breaker that checks the replication delay and will send the query to a replica before the 5 seconds has passed. This is specific to our application and therefore not something Rails should be doing for you. You'll need to test and implement more robust handling of when to switch based on your infrastructure. The auto switcher in Rails is meant to be a basic implementation / API that acts as a guide for how to implement autoswitching. The impementation here is meant to be strict enough that you know how to implement your own resolver and operations classes but flexible enough that we're not telling you how to do it. The middleware is not included automatically and can be installed in your application with the classes you want to use for the resolver and operations passed in. If you don't pass any classes into the middleware the Rails default Resolver and Session classes will be used. The Resolver decides what parameters define when to switch, Operations sets timestamps for the Resolver to read from. For example you may want to use cookies instead of a session so you'd implement a Resolver::Cookies class and pass that into the middleware via configuration options. ``` config.active_record.database_selector = { delay: 2.seconds } config.active_record.database_resolver = MyResolver config.active_record.database_operations = MyResolver::MyCookies ``` Your classes can inherit from the existing classes and reimplment the methods (or implement more methods) that you need to do the switching. You only need to implement methods that you want to change. For example if you wanted to set the session token for the last read from a replica you would reimplement the `read_from_replica` method in your resolver class and implement a method that updates a new timestamp in your operations class.
2019-01-17 13:33:48 -05:00
initializer "active_record.database_selector" do
if options = config.active_record.delete(:database_selector)
Adds basic automatic database switching to Rails The following PR adds behavior to Rails to allow an application to automatically switch it's connection from the primary to the replica. A request will be sent to the replica if: * The request is a read request (`GET` or `HEAD`) * AND It's been 2 seconds since the last write to the database (because we don't want to send a user to a replica if the write hasn't made it to the replica yet) A request will be sent to the primary if: * It's not a GET/HEAD request (ie is a POST, PATCH, etc) * Has been less than 2 seconds since the last write to the database The implementation that decides when to switch reads (the 2 seconds) is "safe" to use in production but not recommended without adequate testing with your infrastructure. At GitHub in addition to the a 5 second delay we have a curcuit breaker that checks the replication delay and will send the query to a replica before the 5 seconds has passed. This is specific to our application and therefore not something Rails should be doing for you. You'll need to test and implement more robust handling of when to switch based on your infrastructure. The auto switcher in Rails is meant to be a basic implementation / API that acts as a guide for how to implement autoswitching. The impementation here is meant to be strict enough that you know how to implement your own resolver and operations classes but flexible enough that we're not telling you how to do it. The middleware is not included automatically and can be installed in your application with the classes you want to use for the resolver and operations passed in. If you don't pass any classes into the middleware the Rails default Resolver and Session classes will be used. The Resolver decides what parameters define when to switch, Operations sets timestamps for the Resolver to read from. For example you may want to use cookies instead of a session so you'd implement a Resolver::Cookies class and pass that into the middleware via configuration options. ``` config.active_record.database_selector = { delay: 2.seconds } config.active_record.database_resolver = MyResolver config.active_record.database_operations = MyResolver::MyCookies ``` Your classes can inherit from the existing classes and reimplment the methods (or implement more methods) that you need to do the switching. You only need to implement methods that you want to change. For example if you wanted to set the session token for the last read from a replica you would reimplement the `read_from_replica` method in your resolver class and implement a method that updates a new timestamp in your operations class.
2019-01-17 13:33:48 -05:00
resolver = config.active_record.delete(:database_resolver)
operations = config.active_record.delete(:database_resolver_context)
config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
Adds basic automatic database switching to Rails The following PR adds behavior to Rails to allow an application to automatically switch it's connection from the primary to the replica. A request will be sent to the replica if: * The request is a read request (`GET` or `HEAD`) * AND It's been 2 seconds since the last write to the database (because we don't want to send a user to a replica if the write hasn't made it to the replica yet) A request will be sent to the primary if: * It's not a GET/HEAD request (ie is a POST, PATCH, etc) * Has been less than 2 seconds since the last write to the database The implementation that decides when to switch reads (the 2 seconds) is "safe" to use in production but not recommended without adequate testing with your infrastructure. At GitHub in addition to the a 5 second delay we have a curcuit breaker that checks the replication delay and will send the query to a replica before the 5 seconds has passed. This is specific to our application and therefore not something Rails should be doing for you. You'll need to test and implement more robust handling of when to switch based on your infrastructure. The auto switcher in Rails is meant to be a basic implementation / API that acts as a guide for how to implement autoswitching. The impementation here is meant to be strict enough that you know how to implement your own resolver and operations classes but flexible enough that we're not telling you how to do it. The middleware is not included automatically and can be installed in your application with the classes you want to use for the resolver and operations passed in. If you don't pass any classes into the middleware the Rails default Resolver and Session classes will be used. The Resolver decides what parameters define when to switch, Operations sets timestamps for the Resolver to read from. For example you may want to use cookies instead of a session so you'd implement a Resolver::Cookies class and pass that into the middleware via configuration options. ``` config.active_record.database_selector = { delay: 2.seconds } config.active_record.database_resolver = MyResolver config.active_record.database_operations = MyResolver::MyCookies ``` Your classes can inherit from the existing classes and reimplment the methods (or implement more methods) that you need to do the switching. You only need to implement methods that you want to change. For example if you wanted to set the session token for the last read from a replica you would reimplement the `read_from_replica` method in your resolver class and implement a method that updates a new timestamp in your operations class.
2019-01-17 13:33:48 -05:00
end
end
initializer "Check for cache versioning support" do
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
if app.config.active_record.cache_versioning && Rails.cache
unless Rails.cache.class.try(:supports_cache_versioning?)
raise <<-end_error
You're using a cache store that doesn't support native cache versioning.
Your best option is to upgrade to a newer version of #{Rails.cache.class}
that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true).
Next best, switch to a different cache store that does support cache versioning:
https://guides.rubyonrails.org/caching_with_rails.html#cache-stores.
To keep using the current cache store, you can turn off cache versioning entirely:
config.active_record.cache_versioning = false
end_error
end
end
end
end
end
initializer "active_record.check_schema_cache_dump" do
if config.active_record.delete(:use_schema_cache_dump)
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
2016-11-13 22:18:14 -05:00
filename = File.join(app.config.paths["db"].first, "schema_cache.yml")
2012-09-22 00:38:18 -04:00
if File.file?(filename)
current_version = ActiveRecord::Migrator.current_version
Refactor migration to move migrations paths to connection Rails has some support for multiple databases but it can be hard to handle migrations with those. The easiest way to implement multiple databases is to contain migrations into their own folder ("db/migrate" for the primary db and "db/seconddb_migrate" for the second db). Without this you would need to write code that allowed you to switch connections in migrations. I can tell you from experience that is not a fun way to implement multiple databases. This refactoring is a pre-requisite for implementing other features related to parallel testing and improved handling for multiple databases. The refactoring here moves the class methods from the `Migrator` class into it's own new class `MigrationContext`. The goal was to move the `migrations_paths` method off of the `Migrator` class and onto the connection. This allows users to do the following in their `database.yml`: ``` development: adapter: mysql2 username: root password: development_seconddb: adapter: mysql2 username: root password: migrations_paths: "db/second_db_migrate" ``` Migrations for the `seconddb` can now be store in the `db/second_db_migrate` directory. Migrations for the primary database are stored in `db/migrate`". The refactoring here drastically reduces the internal API for migrations since we don't need to pass `migrations_paths` around to every single method. Additionally this change does not require any Rails applications to make changes unless they want to use the new public API. All of the class methods from the `Migrator` class were `nodoc`'d except for the `migrations_paths` and `migrations_path` getter/setters respectively.
2018-01-10 10:25:13 -05:00
next if current_version.nil?
2016-11-13 22:18:14 -05:00
cache = YAML.load(File.read(filename))
if cache.version == current_version
connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
end
end
end
end
end
end
initializer "active_record.define_attribute_methods" do |app|
config.after_initialize do
ActiveSupport.on_load(:active_record) do
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
initializer "active_record.warn_on_records_fetched_greater_than" do
if config.active_record.warn_on_records_fetched_greater_than
ActiveSupport.on_load(:active_record) do
require "active_record/relation/record_fetch_warning"
end
end
end
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
configs = app.config.active_record
represent_boolean_as_integer = configs.sqlite3.delete(:represent_boolean_as_integer)
unless represent_boolean_as_integer.nil?
ActiveSupport.on_load(:active_record_sqlite3adapter) do
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer
end
end
configs.delete(:sqlite3)
configs.each do |k, v|
send "#{k}=", v
end
end
end
# This sets the database configuration from Configuration#database_configuration
# and then establishes the connection.
2015-10-05 22:56:02 -04:00
initializer "active_record.initialize_database" do
ActiveSupport.on_load(:active_record) do
self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler }
self.configurations = Rails.application.config.database_configuration
establish_connection
end
end
# Expose database runtime to controller for logging.
2013-03-14 01:17:13 -04:00
initializer "active_record.log_runtime" do
require "active_record/railties/controller_runtime"
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
end
end
initializer "active_record.collection_cache_association_loading" do
require "active_record/railties/collection_cache_association_loading"
ActiveSupport.on_load(:action_view) do
ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading)
end
end
initializer "active_record.set_reloader_hooks" do
2012-06-15 07:09:19 -04:00
ActiveSupport.on_load(:active_record) do
ActiveSupport::Reloader.before_class_unload do
if ActiveRecord::Base.connected?
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.clear_reloadable_connections!
end
end
end
end
initializer "active_record.set_executor_hooks" do
ActiveRecord::QueryCache.install_executor_hooks
end
initializer "active_record.add_watchable_files" do |app|
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
initializer "active_record.clear_active_connections" do
config.after_initialize do
ActiveSupport.on_load(:active_record) do
2017-11-25 00:35:13 -05:00
# Ideally the application doesn't connect to the database during boot,
# but sometimes it does. In case it did, we want to empty out the
# connection pools so that a non-database-using process (e.g. a master
# process in a forking server model) doesn't retain a needless
# connection. If it was needed, the incremental cost of reestablishing
# this connection is trivial: the rest of the pool would need to be
# populated anyway.
clear_active_connections!
2017-11-25 00:35:13 -05:00
flush_idle_connections!
end
end
end
initializer "active_record.set_filter_attributes" do
ActiveSupport.on_load(:active_record) do
self.filter_attributes += Rails.application.config.filter_parameters
end
end
end
end