Extract internal ActiveSupport::ConfigurationFile object

Rails has a number of places where a YAML configuration file is read,
then ERB is evaluated and finally the YAML is parsed.

This consolidates that into one common class.

Co-authored-by: Kasper Timm Hansen <kaspth@gmail.com>
This commit is contained in:
Kurtis Rainbolt-Greene 2019-10-05 17:37:23 -07:00 committed by Kasper Timm Hansen
parent a6cf649250
commit ef7599fe91
No known key found for this signature in database
GPG Key ID: 191153215EDA53D8
10 changed files with 62 additions and 55 deletions

@ -1,7 +1,6 @@
# frozen_string_literal: true
require "erb"
require "yaml"
require "active_support/configuration_file"
module ActiveRecord
class FixtureSet
@ -51,24 +50,14 @@ def config_row
def raw_rows
@raw_rows ||= begin
data = YAML.load(render(IO.read(@file)))
data = ActiveSupport::ConfigurationFile.parse(@file, context:
ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding)
data ? validate(data).to_a : []
rescue ArgumentError, Psych::SyntaxError => error
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at https://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
rescue RuntimeError => error
raise Fixture::FormatError, error.message
end
end
def prepare_erb(content)
erb = ERB.new(content)
erb.filename = @file
erb
end
def render(content)
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
prepare_erb(content).result(context.get_binding)
end
# Validate our unmarshalled data.
def validate(data)
unless Hash === data || YAML::Omap === data

@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_record"
require "active_support/configuration_file"
databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
@ -369,7 +370,7 @@ db_namespace = namespace :db do
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML.load(ERB.new(IO.read(file)).result)
if data = ActiveSupport::ConfigurationFile.parse(file)
data.each_key do |key|
key_id = ActiveRecord::FixtureSet.identify(key)

@ -137,12 +137,6 @@ def test_extracts_model_class_from_config_row
end
end
def test_erb_filename
filename = "filename.yaml"
erb = File.new(filename).send(:prepare_erb, "<% Rails.env %>\n")
assert_equal erb.filename, filename
end
private
def tmp_yaml(name, contents)
t = Tempfile.new name

@ -1,9 +1,8 @@
# frozen_string_literal: true
require "yaml"
require "erb"
require "fileutils"
require "pathname"
require "active_support/configuration_file"
module ARTest
class << self
@ -21,8 +20,7 @@ def read_config
FileUtils.cp TEST_ROOT + "/config.example.yml", config_file
end
erb = ERB.new(config_file.read)
expand_config(YAML.parse(erb.result(binding)).transform)
expand_config ActiveSupport::ConfigurationFile.parse(config_file)
end
def expand_config(config)

@ -106,17 +106,10 @@ class Engine < Rails::Engine # :nodoc:
ActiveSupport.on_load(:active_storage_blob) do
configs = Rails.configuration.active_storage.service_configurations ||=
begin
config_file = Pathname.new(Rails.root.join("config/storage.yml"))
config_file = Rails.root.join("config/storage.yml")
raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
require "yaml"
require "erb"
YAML.load(ERB.new(config_file.read).result) || {}
rescue Psych::SyntaxError => e
raise "YAML syntax error occurred while parsing #{config_file}. " \
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{e.message}"
ActiveSupport::ConfigurationFile.parse(config_file)
end
ActiveStorage::Blob.services = ActiveStorage::Service::Registry.new(configs)

@ -8,6 +8,7 @@
require "active_support/test_case"
require "active_support/core_ext/object/try"
require "active_support/testing/autorun"
require "active_support/configuration_file"
require "active_storage/service/mirror_service"
require "image_processing/mini_magick"
@ -23,11 +24,8 @@
# Filter out the backtrace from minitest while preserving the one from other libraries.
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
require "yaml"
SERVICE_CONFIGURATIONS = begin
erb = ERB.new(Pathname.new(File.expand_path("service/configurations.yml", __dir__)).read)
configuration = YAML.load(erb.result) || {}
configuration.deep_symbolize_keys
ActiveSupport::ConfigurationFile.parse(File.expand_path("service/configurations.yml", __dir__)).deep_symbolize_keys
rescue Errno::ENOENT
puts "Missing service configuration file in test/service/configurations.yml"
{}

@ -35,6 +35,7 @@ module ActiveSupport
autoload :Concern
autoload :ActionableError
autoload :ConfigurationFile
autoload :CurrentAttributes
autoload :Dependencies
autoload :DescendantsTracker

@ -0,0 +1,42 @@
# frozen_string_literal: true
module ActiveSupport
# Reads a YAML configuration file, evaluating any ERB, then
# parsing the resulting YAML.
#
# Warns in case of YAML confusing characters, like invisible
# non-breaking spaces.
class ConfigurationFile # :nodoc:
class FormatError < StandardError; end
def initialize(content_path)
@content_path = content_path
@content = read content_path
end
def self.parse(content_path, **options)
new(content_path).parse(**options)
end
def parse(context: nil, **options)
yaml = context ? ERB.new(@content).result(context) : ERB.new(@content).result
YAML.load(yaml, **options) || {}
rescue Psych::SyntaxError => error
raise "YAML syntax error occurred while parsing #{@content_path}. " \
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{error.message}"
end
private
def read(content_path)
require "yaml"
require "erb"
File.read(content_path).tap do |content|
if content.match?(/\U+A0/)
warn "File contains invisible non-breaking spaces, you may want to remove those"
end
end
end
end
end

@ -8,6 +8,7 @@
require "active_support/encrypted_configuration"
require "active_support/deprecation"
require "active_support/hash_with_indifferent_access"
require "active_support/configuration_file"
require "rails/engine"
require "rails/secrets"
@ -224,7 +225,7 @@ def config_for(name, env: Rails.env)
if yaml.exist?
require "erb"
all_configs = YAML.load(ERB.new(yaml.read).result, symbolize_names: true) || {}
all_configs = ActiveSupport::ConfigurationFile.parse(yaml, symbolize_names: true)
config, shared = all_configs[env.to_sym], all_configs[:shared]
if config.is_a?(Hash)
@ -235,10 +236,6 @@ def config_for(name, env: Rails.env)
else
raise "Could not load configuration. No such file - #{yaml}"
end
rescue Psych::SyntaxError => error
raise "YAML syntax error occurred while parsing #{yaml}. " \
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{error.message}"
end
# Stores some of the Rails initial environment parameters which

@ -3,6 +3,7 @@
require "ipaddr"
require "active_support/core_ext/kernel/reporting"
require "active_support/file_update_checker"
require "active_support/configuration_file"
require "rails/engine/configuration"
require "rails/source_annotation_extractor"
@ -247,12 +248,9 @@ def database_configuration
path = paths["config/database"].existent.first
yaml = Pathname.new(path) if path
config = if yaml && yaml.exist?
require "yaml"
require "erb"
loaded_yaml = YAML.load(ERB.new(yaml.read).result) || {}
shared = loaded_yaml.delete("shared")
if shared
config = if yaml&.exist?
loaded_yaml = ActiveSupport::ConfigurationFile.parse(yaml)
if (shared = loaded_yaml.delete("shared"))
loaded_yaml.each do |_k, values|
values.reverse_merge!(shared)
end
@ -267,10 +265,6 @@ def database_configuration
end
config
rescue Psych::SyntaxError => e
raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{e.message}"
rescue => e
raise e, "Cannot load database configuration:\n#{e.message}", e.backtrace
end