From 37d1429ab1da78a3b8afcfcec8a135ac85cd897a Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 5 Mar 2019 18:45:06 -0500 Subject: [PATCH] Load YAML for rake tasks without parsing ERB This change adds a new method that loads the YAML for the database config without parsing the ERB. This may seem odd but bear with me: When we added the ability to have rake tasks for multiple databases we started looping through the configurations to collect the namespaces so we could do `rake db:create:my_second_db`. See #32274. This caused a problem where if you had `Rails.config.max_threads` set in your database.yml it will blow up because the environment that defines `max_threads` isn't loaded during `rake -T`. See #35468. We tried to fix this by adding the ability to just load the YAML and ignore ERB all together but that caused a bug in GitHub's YAML loading where if you used multi-line ERB the YAML was invalid. That led us to reverting some changes in #33748. After trying to resolve this a bunch of ways `@tenderlove` came up with replacing the ERB values so that we don't need to load the environment but we also can load the YAML. This change adds a DummyCompiler for ERB that will replace all the values so we can load the database yaml and create the rake tasks. Nothing else uses this method so it's "safe". DO NOT use this method in your application. Fixes #35468 --- .../lib/active_record/tasks/database_tasks.rb | 2 +- .../lib/rails/application/configuration.rb | 20 +++++++++++++++++++ .../rails/application/dummy_erb_compiler.rb | 16 +++++++++++++++ railties/test/application/rake/dbs_test.rb | 15 +++----------- 4 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 railties/lib/rails/application/dummy_erb_compiler.rb diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index ef02ca8190322..a8433fa0db1ab 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -142,7 +142,7 @@ def create_all end def for_each - databases = Rails.application.config.database_configuration + databases = Rails.application.config.load_database_yaml database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env) # if this is a single database application we don't want tasks for each primary database diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index af3ec3606405a..83a7b6cf01d0a 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -184,6 +184,26 @@ def paths end end + # Load the database YAML without evaluating ERB. This allows us to + # create the rake tasks for multiple databases without filling in the + # configuration values or loading the environment. Do not use this + # method. + # + # This uses a DummyERB custom compiler so YAML can ignore the ERB + # tags and load the database.yml for the rake tasks. + def load_database_yaml # :nodoc: + if path = paths["config/database"].existent.first + require "rails/application/dummy_erb_compiler" + + yaml = Pathname.new(path) + erb = DummyERB.new(yaml.read) + + YAML.load(erb.result) + else + {} + end + end + # Loads and returns the entire raw configuration of database from # values stored in config/database.yml. def database_configuration diff --git a/railties/lib/rails/application/dummy_erb_compiler.rb b/railties/lib/rails/application/dummy_erb_compiler.rb new file mode 100644 index 0000000000000..4e925269694a5 --- /dev/null +++ b/railties/lib/rails/application/dummy_erb_compiler.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# These classes are used to strip out the ERB configuration +# values so we can evaluate the database.yml without evaluating +# the ERB values. +class DummyERB < ERB # :nodoc: + def make_compiler(trim_mode) + DummyCompiler.new trim_mode + end +end + +class DummyCompiler < ERB::Compiler # :nodoc: + def compile_content(stag, out) + out.push "_erbout << 'dummy_compiler'" + end +end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index a8ec1f36635f3..07d57dea8a60e 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -54,26 +54,17 @@ def db_create_and_drop(expected_database, environment_loaded: true) test "db:create and db:drop respect environment setting" do app_file "config/database.yml", <<-YAML development: - database: db/development.sqlite3 + database: <%= Rails.application.config.database %> adapter: sqlite3 YAML app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do - config.read_encrypted_secrets = true + config.database = "db/development.sqlite3" end RUBY - app_file "lib/tasks/check_env.rake", <<-RUBY - Rake::Task["db:create"].enhance do - File.write("tmp/config_value", Rails.application.config.read_encrypted_secrets) - end - RUBY - - db_create_and_drop("db/development.sqlite3", environment_loaded: false) do - assert File.exist?("tmp/config_value") - assert_equal "true", File.read("tmp/config_value") - end + db_create_and_drop("db/development.sqlite3", environment_loaded: false) end def with_database_existing