runger_config 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +562 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +1121 -0
  5. data/lib/anyway/auto_cast.rb +53 -0
  6. data/lib/anyway/config.rb +473 -0
  7. data/lib/anyway/dynamic_config.rb +31 -0
  8. data/lib/anyway/ejson_parser.rb +40 -0
  9. data/lib/anyway/env.rb +73 -0
  10. data/lib/anyway/ext/deep_dup.rb +48 -0
  11. data/lib/anyway/ext/deep_freeze.rb +44 -0
  12. data/lib/anyway/ext/flatten_names.rb +37 -0
  13. data/lib/anyway/ext/hash.rb +40 -0
  14. data/lib/anyway/ext/string_constantize.rb +24 -0
  15. data/lib/anyway/loaders/base.rb +21 -0
  16. data/lib/anyway/loaders/doppler.rb +63 -0
  17. data/lib/anyway/loaders/ejson.rb +89 -0
  18. data/lib/anyway/loaders/env.rb +18 -0
  19. data/lib/anyway/loaders/yaml.rb +84 -0
  20. data/lib/anyway/loaders.rb +79 -0
  21. data/lib/anyway/option_parser_builder.rb +29 -0
  22. data/lib/anyway/optparse_config.rb +92 -0
  23. data/lib/anyway/rails/autoload.rb +42 -0
  24. data/lib/anyway/rails/config.rb +23 -0
  25. data/lib/anyway/rails/loaders/credentials.rb +64 -0
  26. data/lib/anyway/rails/loaders/secrets.rb +37 -0
  27. data/lib/anyway/rails/loaders/yaml.rb +9 -0
  28. data/lib/anyway/rails/loaders.rb +5 -0
  29. data/lib/anyway/rails/settings.rb +83 -0
  30. data/lib/anyway/rails.rb +24 -0
  31. data/lib/anyway/railtie.rb +28 -0
  32. data/lib/anyway/rbs.rb +92 -0
  33. data/lib/anyway/settings.rb +111 -0
  34. data/lib/anyway/testing/helpers.rb +36 -0
  35. data/lib/anyway/testing.rb +13 -0
  36. data/lib/anyway/tracing.rb +188 -0
  37. data/lib/anyway/type_casting.rb +144 -0
  38. data/lib/anyway/utils/deep_merge.rb +21 -0
  39. data/lib/anyway/utils/which.rb +18 -0
  40. data/lib/anyway/version.rb +5 -0
  41. data/lib/anyway.rb +3 -0
  42. data/lib/anyway_config.rb +54 -0
  43. data/lib/generators/anyway/app_config/USAGE +9 -0
  44. data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
  45. data/lib/generators/anyway/config/USAGE +13 -0
  46. data/lib/generators/anyway/config/config_generator.rb +51 -0
  47. data/lib/generators/anyway/config/templates/config.rb.tt +12 -0
  48. data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
  49. data/lib/generators/anyway/install/USAGE +4 -0
  50. data/lib/generators/anyway/install/install_generator.rb +47 -0
  51. data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
  52. data/sig/anyway_config.rbs +149 -0
  53. data/sig/manifest.yml +6 -0
  54. metadata +202 -0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Ext
5
+ # Add #deep_freeze to hashes and arrays
6
+ module DeepFreeze
7
+ refine ::Hash do
8
+ def deep_freeze
9
+ freeze
10
+ each_value do |value|
11
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
12
+ end
13
+ end
14
+ end
15
+
16
+ refine ::Array do
17
+ def deep_freeze
18
+ freeze
19
+ each do |value|
20
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
21
+ end
22
+ end
23
+ end
24
+
25
+ begin
26
+ require "active_support/core_ext/hash/indifferent_access"
27
+ rescue LoadError
28
+ end
29
+
30
+ if defined?(::ActiveSupport::HashWithIndifferentAccess)
31
+ refine ::ActiveSupport::HashWithIndifferentAccess do
32
+ def deep_freeze
33
+ freeze
34
+ each_value do |value|
35
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ using self
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Ext
5
+ # Convert Hash with mixed array and hash values to an
6
+ # array of paths.
7
+ module FlattenNames
8
+ refine ::Array do
9
+ def flatten_names(prefix, buf)
10
+ if empty?
11
+ buf << :"#{prefix}"
12
+ return buf
13
+ end
14
+
15
+ each_with_object(buf) do |name, acc|
16
+ if name.is_a?(::Symbol)
17
+ acc << :"#{prefix}.#{name}"
18
+ else
19
+ name.flatten_names(prefix, acc)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ refine ::Hash do
26
+ def flatten_names(prefix = nil, buf = [])
27
+ each_with_object(buf) do |(k, v), acc|
28
+ parent = prefix ? "#{prefix}.#{k}" : k
29
+ v.flatten_names(parent, acc)
30
+ end
31
+ end
32
+ end
33
+
34
+ using self
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Ext
5
+ # Extend Hash through refinements
6
+ module Hash
7
+ refine ::Hash do
8
+ def stringify_keys!
9
+ keys.each do |key|
10
+ value = delete(key)
11
+ self[key.to_s] = value
12
+ end
13
+
14
+ self
15
+ end
16
+
17
+ def bury(val, *path)
18
+ raise ArgumentError, "No path specified" if path.empty?
19
+ raise ArgumentError, "Path cannot contain nil" if path.compact.size != path.size
20
+
21
+ last_key = path.pop
22
+ hash = path.reduce(self) do |hash, k|
23
+ hash[k] = {} unless hash.key?(k) && hash[k].is_a?(::Hash)
24
+ hash[k]
25
+ end
26
+
27
+ hash[last_key] = val
28
+ end
29
+
30
+ def deep_transform_keys(&block)
31
+ each_with_object({}) do |(key, value), result|
32
+ result[yield(key)] = value.is_a?(::Hash) ? value.deep_transform_keys(&block) : value
33
+ end
34
+ end
35
+ end
36
+
37
+ using self
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Ext
5
+ # Add simple safe_constantize method to String
6
+ module StringConstantize
7
+ refine String do
8
+ def safe_constantize
9
+ names = split("::")
10
+
11
+ return nil if names.empty?
12
+
13
+ # Remove the first blank element in case of '::ClassName' notation.
14
+ names.shift if names.size > 1 && names.first.empty?
15
+
16
+ names.inject(Object) do |constant, name|
17
+ break if constant.nil?
18
+ constant.const_get(name, false) if constant.const_defined?(name, false)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Loaders
5
+ class Base
6
+ include Tracing
7
+
8
+ class << self
9
+ def call(local: Anyway::Settings.use_local_files, **opts)
10
+ new(local:).call(**opts)
11
+ end
12
+ end
13
+
14
+ def initialize(local:)
15
+ @local = local
16
+ end
17
+
18
+ def use_local?() = @local == true
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "net/http"
5
+ require "json"
6
+
7
+ module Anyway
8
+ using RubyNext
9
+
10
+ module Loaders
11
+ class Doppler < Base
12
+ class RequestError < StandardError; end
13
+
14
+ class << self
15
+ attr_accessor :download_url
16
+ attr_writer :token
17
+
18
+ def token
19
+ @token || ENV["DOPPLER_TOKEN"]
20
+ end
21
+ end
22
+
23
+ self.download_url = "https://api.doppler.com/v3/configs/config/secrets/download"
24
+
25
+ def call(env_prefix:, **_options)
26
+ env_payload = parse_doppler_response(url: Doppler.download_url, token: Doppler.token)
27
+
28
+ env = ::Anyway::Env.new(type_cast: ::Anyway::NoCast, env_container: env_payload)
29
+
30
+ env.fetch_with_trace(env_prefix).then do |(conf, trace)|
31
+ Tracing.current_trace&.merge!(trace)
32
+ conf
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def parse_doppler_response(url:, token:)
39
+ response = fetch_doppler_config(url, token)
40
+
41
+ unless response.is_a?(Net::HTTPSuccess)
42
+ raise RequestError, "#{response.code} #{response.message}"
43
+ end
44
+
45
+ JSON.parse(response.read_body)
46
+ end
47
+
48
+ def fetch_doppler_config(url, token)
49
+ uri = URI.parse(url)
50
+ raise "Doppler token is required to load configuration from Doppler" if token.nil?
51
+
52
+ http = Net::HTTP.new(uri.host, uri.port)
53
+ http.use_ssl = true if uri.scheme == "https"
54
+
55
+ request = Net::HTTP::Get.new(uri)
56
+ request["Accept"] = "application/json"
57
+ request["Authorization"] = "Bearer #{token}"
58
+
59
+ http.request(request)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyway/ejson_parser"
4
+
5
+ module Anyway
6
+ module Loaders
7
+ class EJSON < Base
8
+ class << self
9
+ attr_accessor :bin_path
10
+ end
11
+
12
+ self.bin_path = "ejson"
13
+
14
+ def call(name:, ejson_namespace: name, ejson_parser: Anyway::EJSONParser.new(EJSON.bin_path), **_options)
15
+ configs = []
16
+
17
+ rel_config_paths.each do |rel_config_path|
18
+ secrets_hash, rel_path =
19
+ extract_hash_from_rel_config_path(
20
+ ejson_parser: ejson_parser,
21
+ rel_config_path: rel_config_path
22
+ )
23
+
24
+ next unless secrets_hash
25
+
26
+ config_hash = if ejson_namespace
27
+ secrets_hash[ejson_namespace]
28
+ else
29
+ secrets_hash.except("_public_key")
30
+ end
31
+
32
+ next unless config_hash.is_a?(Hash)
33
+
34
+ configs <<
35
+ trace!(:ejson, path: rel_path) do
36
+ config_hash
37
+ end
38
+ end
39
+
40
+ return {} if configs.empty?
41
+
42
+ configs.inject do |result_config, next_config|
43
+ Utils.deep_merge!(result_config, next_config)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def rel_config_paths
50
+ chain = [environmental_rel_config_path]
51
+
52
+ chain << "secrets.local.ejson" if use_local?
53
+
54
+ chain
55
+ end
56
+
57
+ def environmental_rel_config_path
58
+ if Settings.current_environment
59
+ # if environment file is absent, then take data from the default one
60
+ [
61
+ "#{Settings.current_environment}/secrets.ejson",
62
+ default_rel_config_path
63
+ ]
64
+ else
65
+ default_rel_config_path
66
+ end
67
+ end
68
+
69
+ def default_rel_config_path
70
+ "secrets.ejson"
71
+ end
72
+
73
+ def extract_hash_from_rel_config_path(ejson_parser:, rel_config_path:)
74
+ rel_config_path = [rel_config_path] unless rel_config_path.is_a?(Array)
75
+
76
+ rel_config_path.each do |rel_conf_path|
77
+ rel_path = "config/#{rel_conf_path}"
78
+ abs_path = "#{Settings.app_root}/#{rel_path}"
79
+
80
+ result = ejson_parser.call(abs_path)
81
+
82
+ return [result, rel_path] if result
83
+ end
84
+
85
+ nil
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using RubyNext
5
+
6
+ module Loaders
7
+ class Env < Base
8
+ def call(env_prefix:, **_options)
9
+ env = ::Anyway::Env.new(type_cast: ::Anyway::NoCast)
10
+
11
+ env.fetch_with_trace(env_prefix).then do |(conf, trace)|
12
+ Tracing.current_trace&.merge!(trace)
13
+ conf
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "anyway/ext/hash"
5
+
6
+ using RubyNext
7
+ using Anyway::Ext::Hash
8
+
9
+ module Anyway
10
+ module Loaders
11
+ class YAML < Base
12
+ def call(config_path:, **_options)
13
+ rel_config_path = relative_config_path(config_path).to_s
14
+ base_config = trace!(:yml, path: rel_config_path) do
15
+ config = load_base_yml(config_path)
16
+ environmental?(config) ? config_with_env(config) : config
17
+ end
18
+
19
+ return base_config unless use_local?
20
+
21
+ local_path = local_config_path(config_path)
22
+ local_config = trace!(:yml, path: relative_config_path(local_path).to_s) { load_local_yml(local_path) }
23
+ Utils.deep_merge!(base_config, local_config)
24
+ end
25
+
26
+ private
27
+
28
+ def environmental?(parsed_yml)
29
+ # strange, but still possible
30
+ return true if Settings.default_environmental_key? && parsed_yml.key?(Settings.default_environmental_key)
31
+ # possible
32
+ return true if !Settings.future.unwrap_known_environments && Settings.current_environment
33
+ # for other environments
34
+ return true if Settings.known_environments&.any? { parsed_yml.key?(_1) }
35
+ # preferred
36
+ parsed_yml.key?(Settings.current_environment)
37
+ end
38
+
39
+ def config_with_env(config)
40
+ env_config = config[Settings.current_environment] || {}
41
+ return env_config unless Settings.default_environmental_key?
42
+
43
+ default_config = config[Settings.default_environmental_key] || {}
44
+ Utils.deep_merge!(default_config, env_config)
45
+ end
46
+
47
+ def parse_yml(path)
48
+ return {} unless File.file?(path)
49
+ require "yaml" unless defined?(::YAML)
50
+
51
+ # By default, YAML load will return `false` when the yaml document is
52
+ # empty. When this occurs, we return an empty hash instead, to match
53
+ # the interface when no config file is present.
54
+ begin
55
+ if defined?(ERB)
56
+ ::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {}
57
+ else
58
+ ::YAML.load_file(path, aliases: true) || {}
59
+ end
60
+ rescue ArgumentError
61
+ if defined?(ERB)
62
+ ::YAML.load(ERB.new(File.read(path)).result) || {}
63
+ else
64
+ ::YAML.load_file(path) || {}
65
+ end
66
+ end
67
+ end
68
+
69
+ alias_method :load_base_yml, :parse_yml
70
+ alias_method :load_local_yml, :parse_yml
71
+
72
+ def local_config_path(path)
73
+ path.sub(".yml", ".local.yml")
74
+ end
75
+
76
+ def relative_config_path(path)
77
+ Pathname.new(path).then do |path|
78
+ return path if path.relative?
79
+ path.relative_path_from(Settings.app_root)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using RubyNext
5
+
6
+ module Loaders
7
+ class Registry
8
+ attr_reader :registry
9
+
10
+ def initialize
11
+ @registry = []
12
+ end
13
+
14
+ def prepend(id, handler = nil, &block)
15
+ handler ||= block
16
+ insert_at(0, id, handler)
17
+ end
18
+
19
+ def append(id, handler = nil, &block)
20
+ handler ||= block
21
+ insert_at(registry.size, id, handler)
22
+ end
23
+
24
+ def insert_before(another_id, id, handler = nil, &block)
25
+ ind = registry.find_index { |(hid, _)| hid == another_id }
26
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
27
+
28
+ handler ||= block
29
+ insert_at(ind, id, handler)
30
+ end
31
+
32
+ def insert_after(another_id, id, handler = nil, &block)
33
+ ind = registry.find_index { |(hid, _)| hid == another_id }
34
+ raise ArgumentError, "Loader with ID #{another_id} hasn't been registered" if ind.nil?
35
+
36
+ handler ||= block
37
+ insert_at(ind + 1, id, handler)
38
+ end
39
+
40
+ def override(id, handler)
41
+ find(id).then do |id_to_handler|
42
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
43
+ id_to_handler[1] = handler
44
+ end
45
+ end
46
+
47
+ def delete(id)
48
+ find(id).then do |id_to_handler|
49
+ raise ArgumentError, "Loader with ID #{id} hasn't been registered" if id_to_handler.nil?
50
+ registry.delete id_to_handler
51
+ end
52
+ end
53
+
54
+ def each(&block)
55
+ registry.each(&block)
56
+ end
57
+
58
+ def freeze() = registry.freeze
59
+
60
+ private
61
+
62
+ def insert_at(index, id, handler)
63
+ raise ArgumentError, "Loader with ID #{id} has been already registered" unless find(id).nil?
64
+
65
+ registry.insert(index, [id, handler])
66
+ end
67
+
68
+ def find(id)
69
+ registry.find { |(hid, _)| hid == id }
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ require "anyway/loaders/base"
76
+ require "anyway/loaders/yaml"
77
+ require "anyway/loaders/env"
78
+ require "anyway/loaders/doppler"
79
+ require "anyway/loaders/ejson"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module Anyway # :nodoc:
6
+ # Initializes the OptionParser instance using the given configuration
7
+ class OptionParserBuilder
8
+ class << self
9
+ def call(options)
10
+ OptionParser.new do |opts|
11
+ options.each do |key, descriptor|
12
+ opts.on(*option_parser_on_args(key, **descriptor)) do |val|
13
+ yield [key, val]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def option_parser_on_args(key, flag: false, desc: nil, type: ::String)
22
+ on_args = ["--#{key.to_s.tr("_", "-")}#{flag ? "" : " VALUE"}"]
23
+ on_args << type unless flag
24
+ on_args << desc unless desc.nil?
25
+ on_args
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyway/option_parser_builder"
4
+
5
+ require "anyway/ext/deep_dup"
6
+
7
+ module Anyway
8
+ using Anyway::Ext::DeepDup
9
+
10
+ # Adds ability to use script options as the source
11
+ # of configuration (via optparse)
12
+ module OptparseConfig
13
+ module ClassMethods
14
+ def ignore_options(*args)
15
+ args.each do |name|
16
+ option_parser_descriptors[name.to_s][:ignore] = true
17
+ end
18
+ end
19
+
20
+ def describe_options(**hargs)
21
+ hargs.each do |name, desc|
22
+ if String === desc
23
+ option_parser_descriptors[name.to_s][:desc] = desc
24
+ else
25
+ option_parser_descriptors[name.to_s].merge!(desc)
26
+ end
27
+ end
28
+ end
29
+
30
+ def flag_options(*args)
31
+ args.each do |name|
32
+ option_parser_descriptors[name.to_s][:flag] = true
33
+ end
34
+ end
35
+
36
+ def extend_options(&block)
37
+ option_parser_extensions << block
38
+ end
39
+
40
+ def option_parser_options
41
+ config_attributes.each_with_object({}) do |key, result|
42
+ descriptor = option_parser_descriptors[key.to_s]
43
+ next if descriptor[:ignore] == true
44
+
45
+ result[key] = descriptor
46
+ end
47
+ end
48
+
49
+ def option_parser_extensions
50
+ return @option_parser_extensions if instance_variable_defined?(:@option_parser_extensions)
51
+
52
+ @option_parser_extensions =
53
+ if superclass < Anyway::Config
54
+ superclass.option_parser_extensions.dup
55
+ else
56
+ []
57
+ end
58
+ end
59
+
60
+ def option_parser_descriptors
61
+ return @option_parser_descriptors if instance_variable_defined?(:@option_parser_descriptors)
62
+
63
+ @option_parser_descriptors =
64
+ if superclass < Anyway::Config
65
+ superclass.option_parser_descriptors.deep_dup
66
+ else
67
+ Hash.new { |h, k| h[k] = {} }
68
+ end
69
+ end
70
+ end
71
+
72
+ def option_parser
73
+ @option_parser ||= OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
74
+ write_config_attr(key, val)
75
+ end.tap do |parser|
76
+ self.class.option_parser_extensions.map do |extension|
77
+ extension.call(parser, self)
78
+ end
79
+ end
80
+ end
81
+
82
+ def parse_options!(options)
83
+ Tracing.with_trace_source(type: :options) do
84
+ option_parser.parse!(options)
85
+ end
86
+ end
87
+
88
+ def self.included(base)
89
+ base.extend ClassMethods
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module is used to detect a Rails application and activate the corresponding plugins
4
+ # when Anyway Config is loaded before Rails (e.g., in config/puma.rb).
5
+ module Anyway
6
+ module Rails
7
+ using RubyNext
8
+
9
+ class << self
10
+ attr_reader :tracer, :name_method
11
+ attr_accessor :disable_postponed_load_warning
12
+
13
+ private
14
+
15
+ def tracepoint_class_callback(event)
16
+ # Ignore singletons
17
+ return if event.self.singleton_class?
18
+
19
+ # We wait till `rails/application/configuration.rb` has been loaded, since we rely on it
20
+ # See https://github.com/palkan/anyway_config/issues/134
21
+ return unless name_method.bind_call(event.self) == "Rails::Application::Configuration"
22
+
23
+ tracer.disable
24
+
25
+ unless disable_postponed_load_warning
26
+ warn "Anyway Config was loaded before Rails. Activating Anyway Config Rails plugins now.\n" \
27
+ "NOTE: Already loaded configs were provisioned without Rails-specific sources."
28
+ end
29
+
30
+ require "anyway/rails"
31
+ end
32
+ end
33
+
34
+ # TruffleRuby doesn't support TracePoint's end event
35
+ unless defined?(::TruffleRuby)
36
+ @tracer = TracePoint.new(:end, &method(:tracepoint_class_callback))
37
+ @tracer.enable
38
+ # Use `Module#name` instead of `self.name` to handle overwritten `name` method
39
+ @name_method = Module.instance_method(:name)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module Anyway
6
+ module Rails
7
+ # Enhance config to be more Railsy-like:
8
+ # – accept hashes with indeferent access
9
+ # - load data from secrets
10
+ # - recognize Rails env when loading from YML
11
+ module Config
12
+ module ClassMethods
13
+ # Make defaults to be a Hash with indifferent access
14
+ def new_empty_config
15
+ {}.with_indifferent_access
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Anyway::Config.prepend Anyway::Rails::Config
23
+ Anyway::Config.singleton_class.prepend Anyway::Rails::Config::ClassMethods