runger_config 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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