dry-configurable 0.15.0 → 0.16.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b464e3a14e23a75467fc56883a5e222ad96139fee156972e58729607113cba55
4
- data.tar.gz: 599d39ad241c3a6c17d27dd3ea8be2d94c1821a1c23ca9dea675126ba6c05ca6
3
+ metadata.gz: 323f300c1cf0cf407272352b81dd0775ac7f855713cd6145fccc9d11dbdae8cf
4
+ data.tar.gz: b8258cfa9a30108831384e575a00e5afd93b9d94942d52799d23013e3753ceaf
5
5
  SHA512:
6
- metadata.gz: 8d306539d687ef4dc2ee1a999008ab3ead0f466cef01cc17b0122bc088d503da4515b906fa1d26449a4c1dd57b82d39cfd2558c4734061025fb6954383364ee3
7
- data.tar.gz: 421f2b5bc4a89b155d889ee4305183a3f97c42ab74db8cd9c41fecbccbb0f11ff739a4a01afa780da4291bf3404e6ed87c224093ee08f8de706ce46d30b39da8
6
+ metadata.gz: d37acc7d39adb1ed1f0da218c5f16ac0e6d09832e70e73220770fa28ad8eff4f21f440a6285b22785de82f76a8df6f444d37024d5059fdc2ba3f3b3006fd19c9
7
+ data.tar.gz: c7888430539dfa30f4518ca159cae0675d2766ec8c27870a5e74c64f04521aa517fea1772bb766f5c9f364d83e24610c7bf7f72c65739adb05b2e9a0ca1230f0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 0.16.0 2022-10-08
4
+
5
+
6
+ ### Added
7
+
8
+ - Support for custom config classes via `config_class:` option (#136 by @solnic)
9
+
10
+ ```ruby
11
+ extend Dry::Configurable(config_class: MyConfig)
12
+ ```
13
+
14
+ Your config class should inherit from `Dry::Configurable::Config`.
15
+ - Return `Dry::Core::Constants::Undefined` (instead of nil) as the value for non-configured settings via a `default_undefined: true` option (#141 by @timriley)
16
+
17
+ ```ruby
18
+ extend Dry::Configurable(default_undefined: true)
19
+ ```
20
+
21
+ You must opt into this feature via the `default_undefined: true` option. Non-configured setting values are still `nil` by default.
22
+
23
+ ### Fixed
24
+
25
+ - Remove exec bit from version.rb (#139 by @Fryguy)
26
+
27
+ ### Changed
28
+
29
+ - Improve memory usage by separating setting definitions from config values (#138 by @timriley)
30
+
31
+ Your usage of dry-configurable may be impacted if you have been accessing objects from `_settings` or the internals of `Dry::Configurable::Config`. `_settings` now returns `Dry::Configurable::Setting` instances, which contain only the details from the setting's definition. Setting _values_ remain in `Dry::Configurable::Config`.
32
+ - Use Zeitwerk to speed up load time (#135 by @solnic)
33
+
34
+ [Compare v0.15.0...v0.16.0](https://github.com/dry-rb/dry-configurable/compare/v0.15.0...v0.16.0)
35
+
3
36
  ## 0.15.0 2022-04-21
4
37
 
5
38
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2021 dry-rb team
3
+ Copyright (c) 2015-2022 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
29
29
  spec.required_ruby_version = ">= 2.7.0"
30
30
 
31
31
  # to update dependencies edit project.yml
32
- spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
33
32
  spec.add_runtime_dependency "dry-core", "~> 0.6"
33
+ spec.add_runtime_dependency "zeitwerk", "~> 2.6"
34
34
 
35
35
  spec.add_development_dependency "bundler"
36
36
  spec.add_development_dependency "rake"
@@ -2,11 +2,6 @@
2
2
 
3
3
  require "set"
4
4
 
5
- require "dry/configurable/constants"
6
- require "dry/configurable/dsl"
7
- require "dry/configurable/methods"
8
- require "dry/configurable/settings"
9
-
10
5
  module Dry
11
6
  module Configurable
12
7
  module ClassMethods
@@ -16,8 +11,17 @@ module Dry
16
11
  def inherited(subclass)
17
12
  super
18
13
 
19
- subclass.instance_variable_set("@_settings", _settings.dup)
20
- subclass.instance_variable_set("@_config", config.dup) if respond_to?(:config)
14
+ subclass.instance_variable_set(:@__config_extension__, __config_extension__)
15
+
16
+ new_settings = _settings.dup
17
+ subclass.instance_variable_set(:@_settings, new_settings)
18
+
19
+ # Only classes **extending** Dry::Configurable have class-level config. When
20
+ # Dry::Configurable is **included**, the class-level config method is undefined because it
21
+ # resides at the instance-level instead (see `Configurable.included`).
22
+ if respond_to?(:config)
23
+ subclass.instance_variable_set(:@config, config.dup_for_settings(new_settings))
24
+ end
21
25
  end
22
26
 
23
27
  # Add a setting to the configuration
@@ -52,7 +56,7 @@ module Dry
52
56
  #
53
57
  # @api public
54
58
  def settings
55
- @settings ||= Set[*_settings.map(&:name)]
59
+ Set[*_settings.map(&:name)]
56
60
  end
57
61
 
58
62
  # Return declared settings
@@ -70,17 +74,25 @@ module Dry
70
74
  #
71
75
  # @api public
72
76
  def config
73
- # The _settings provided to the Config remain shared between the class and the
74
- # Config. This allows settings defined _after_ accessing the config to become
75
- # available in subsequent accesses to the config. The config is duped when
76
- # subclassing to ensure it remains distinct between subclasses and parent classes
77
- # (see `.inherited` above).
78
- @config ||= Config.new(_settings)
77
+ @config ||= __config_build__
78
+ end
79
+
80
+ # @api private
81
+ def __config_build__(settings = _settings)
82
+ __config_extension__.config_class.new(settings)
83
+ end
84
+
85
+ # @api private
86
+ def __config_extension__
87
+ @__config_extension__
79
88
  end
80
89
 
81
90
  # @api private
82
91
  def __config_dsl__
83
- @__config_dsl__ ||= DSL.new
92
+ @__config_dsl__ ||= DSL.new(
93
+ config_class: __config_extension__.config_class,
94
+ default_undefined: __config_extension__.default_undefined
95
+ )
84
96
  end
85
97
 
86
98
  # @api private
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable/setting"
4
- require "dry/configurable/settings"
5
-
6
3
  module Dry
7
4
  module Configurable
8
5
  # Setting compiler used internally by the DSL
@@ -32,7 +29,9 @@ module Dry
32
29
  # @api private
33
30
  def visit_nested(node)
34
31
  parent, children = node
35
- visit(parent).nested(call(children))
32
+ name, opts = parent[1]
33
+
34
+ Setting.new(name, **opts, children: Settings.new(call(children)))
36
35
  end
37
36
  end
38
37
  end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/map"
3
+ require "dry/core/constants"
4
4
 
5
5
  require "dry/core/equalizer"
6
6
 
7
- require "dry/configurable/constants"
8
- require "dry/configurable/errors"
9
-
10
7
  module Dry
11
8
  module Configurable
12
9
  # Config exposes setting values through a convenient API
@@ -19,12 +16,17 @@ module Dry
19
16
  attr_reader :_settings
20
17
 
21
18
  # @api private
22
- attr_reader :_resolved
19
+ attr_reader :_values
23
20
 
24
21
  # @api private
25
- def initialize(settings)
22
+ def initialize(settings, values: {})
26
23
  @_settings = settings
27
- @_resolved = Concurrent::Map.new
24
+ @_values = values
25
+ end
26
+
27
+ # @api private
28
+ def dup_for_settings(settings)
29
+ self.class.new(settings, values: dup_values)
28
30
  end
29
31
 
30
32
  # Get config value by a key
@@ -34,9 +36,18 @@ module Dry
34
36
  # @return Config value
35
37
  def [](name)
36
38
  name = name.to_sym
37
- raise ArgumentError, "+#{name}+ is not a setting name" unless _settings.key?(name)
38
39
 
39
- _settings[name].value
40
+ unless (setting = _settings[name])
41
+ raise ArgumentError, "+#{name}+ is not a setting name"
42
+ end
43
+
44
+ _values.fetch(name) {
45
+ # When reading values, only capture cloneable (i.e. mutable) values in local state, making
46
+ # it easier to determine which values have actually been changed vs just read
47
+ setting.to_value.tap { |value|
48
+ _values[name] = value if setting.cloneable?
49
+ }
50
+ }
40
51
  end
41
52
 
42
53
  # Set config value.
@@ -45,7 +56,15 @@ module Dry
45
56
  # @param [String,Symbol] name
46
57
  # @param [Object] value
47
58
  def []=(name, value)
48
- public_send(:"#{name}=", value)
59
+ raise FrozenConfig, "Cannot modify frozen config" if frozen?
60
+
61
+ name = name.to_sym
62
+
63
+ unless (setting = _settings[name])
64
+ raise ArgumentError, "+#{name}+ is not a setting name"
65
+ end
66
+
67
+ _values[name] = setting.constructor.(value)
49
68
  end
50
69
 
51
70
  # Update config with new values
@@ -70,61 +89,96 @@ module Dry
70
89
  self
71
90
  end
72
91
 
73
- # Dump config into a hash
92
+ # Returns true if the value for the given key has been set on this config.
93
+ #
94
+ # For simple values, this returns true if the value has been explicitly assigned.
95
+ #
96
+ # For cloneable (mutable) values, since these are captured on read, returns true if the value
97
+ # does not compare equally to its corresdponing default value. This relies on these objects
98
+ # having functioning `#==` checks.
99
+ #
100
+ # @return [Bool]
101
+ #
102
+ # @api public
103
+ def configured?(key)
104
+ if _settings[key].cloneable? && _values.key?(key)
105
+ return _values[key] != _settings[key].to_value
106
+ end
107
+
108
+ _values.key?(key)
109
+ end
110
+
111
+ # Returns the current config values.
112
+ #
113
+ # Nested configs remain in their {Config} instances.
74
114
  #
75
115
  # @return [Hash]
76
116
  #
77
117
  # @api public
78
118
  def values
79
- _settings
80
- .map { |setting| [setting.name, setting.value] }
81
- .map { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
82
- .to_h
119
+ _settings.to_h { |setting| [setting.name, self[setting.name]] }
120
+ end
121
+
122
+ # Returns config values as a hash, with nested values also converted from {Config} instances
123
+ # into hashes.
124
+ #
125
+ # @return [Hash]
126
+ #
127
+ # @api public
128
+ def to_h
129
+ values.to_h { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
83
130
  end
84
- alias_method :to_h, :values
85
131
 
86
132
  # @api private
87
133
  def finalize!(freeze_values: false)
88
- _settings.finalize!(freeze_values: freeze_values)
134
+ values.each_value do |value|
135
+ if value.is_a?(self.class)
136
+ value.finalize!(freeze_values: freeze_values)
137
+ elsif freeze_values
138
+ value.freeze
139
+ end
140
+ end
141
+
89
142
  freeze
90
143
  end
91
144
 
92
145
  # @api private
93
146
  def pristine
94
- self.class.new(_settings.pristine)
95
- end
96
-
97
- # @api private
98
- def respond_to_missing?(meth, include_private = false)
99
- super || _settings.key?(resolve(meth))
147
+ self.class.new(_settings)
100
148
  end
101
149
 
102
150
  private
103
151
 
104
- # @api private
105
- def method_missing(meth, *args)
106
- setting = _settings[resolve(meth)]
152
+ def method_missing(name, *args)
153
+ setting_name = setting_name_from_method(name)
154
+ setting = _settings[setting_name]
107
155
 
108
156
  super unless setting
109
157
 
110
- if setting.writer?(meth)
111
- raise FrozenConfig, "Cannot modify frozen config" if frozen?
112
-
113
- _settings << setting.with(input: args[0])
158
+ if name.end_with?("=")
159
+ self[setting_name] = args[0]
114
160
  else
115
- setting.value
161
+ self[setting_name]
116
162
  end
117
163
  end
118
164
 
119
- # @api private
120
- def resolve(meth)
121
- _resolved.fetch(meth) { _resolved[meth] = meth.to_s.tr("=", "").to_sym }
165
+ def respond_to_missing?(meth, include_private = false)
166
+ _settings.key?(setting_name_from_method(meth)) || super
167
+ end
168
+
169
+ def setting_name_from_method(method_name)
170
+ method_name.to_s.tr("=", "").to_sym
171
+ end
172
+
173
+ def dup_values
174
+ _values.each_with_object({}) { |(key, val), dup_hsh|
175
+ dup_hsh[key] = _settings[key].cloneable? ? val.dup : val
176
+ }
122
177
  end
123
178
 
124
- # @api private
125
179
  def initialize_copy(source)
126
180
  super
127
- @_settings = source._settings.dup
181
+ @_values = source.__send__(:dup_values)
128
182
  end
129
183
  end
130
184
  end
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable/constants"
4
- require "dry/configurable/flags"
5
- require "dry/configurable/setting"
6
- require "dry/configurable/settings"
7
- require "dry/configurable/compiler"
8
3
  require "dry/core/deprecations"
9
4
 
10
5
  module Dry
@@ -15,16 +10,16 @@ module Dry
15
10
  class DSL
16
11
  VALID_NAME = /\A[a-z_]\w*\z/i.freeze
17
12
 
18
- # @api private
19
13
  attr_reader :compiler
20
14
 
21
- # @api private
22
15
  attr_reader :ast
23
16
 
24
- # @api private
25
- def initialize(&block)
17
+ attr_reader :options
18
+
19
+ def initialize(**options, &block)
26
20
  @compiler = Compiler.new
27
21
  @ast = []
22
+ @options = options
28
23
  instance_exec(&block) if block
29
24
  end
30
25
 
@@ -33,106 +28,15 @@ module Dry
33
28
  # @see ClassMethods.setting
34
29
  # @api private
35
30
  # @return Setting
36
- def setting(name, default = Undefined, **options, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
31
+ def setting(name, **options, &block) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
37
32
  unless VALID_NAME.match?(name.to_s)
38
33
  raise ArgumentError, "#{name} is not a valid setting name"
39
34
  end
40
35
 
41
- if default != Undefined
42
- if Dry::Configurable.warn_on_setting_positional_default
43
- Dry::Core::Deprecations.announce(
44
- "default value as positional argument to settings",
45
- "Provide a `default:` keyword argument instead",
46
- tag: "dry-configurable",
47
- uplevel: 2
48
- )
49
- end
50
-
51
- options = options.merge(default: default)
52
- end
53
-
54
- if RUBY_VERSION < "3.0" &&
55
- default == Undefined &&
56
- (valid_opts, invalid_opts = valid_and_invalid_options(options)) &&
57
- invalid_opts.any? &&
58
- valid_opts.none?
59
- # In Ruby 2.6 and 2.7, when a hash is given as the second positional argument
60
- # (i.e. the hash is intended to be the setting's default value), and there are
61
- # no other keyword arguments given, the hash is assigned to the `options`
62
- # variable instead of `default`.
63
- #
64
- # For example, for this setting:
65
- #
66
- # setting :hash_setting, {my_hash: true}
67
- #
68
- # We'll have a `default` of `Undefined` and an `options` of `{my_hash: true}`
69
- #
70
- # If any additional keyword arguments are provided, e.g.:
71
- #
72
- # setting :hash_setting, {my_hash: true}, reader: true
73
- #
74
- # Then we'll have a `default` of `{my_hash: true}` and an `options` of `{reader:
75
- # true}`, which is what we want.
76
- #
77
- # To work around that first case and ensure our (deprecated) backwards
78
- # compatibility holds for Ruby 2.6 and 2.7, we extract all invalid options from
79
- # `options`, and if there are no remaining valid options (i.e. if there were no
80
- # keyword arguments given), then we can infer the invalid options to be a
81
- # default hash value for the setting.
82
- #
83
- # This approach also preserves the behavior of raising an ArgumentError when a
84
- # distinct hash is _not_ intentionally provided as the second positional
85
- # argument (i.e. it's not enclosed in braces), and instead invalid keyword
86
- # arguments are given alongside valid ones. So this setting:
87
- #
88
- # setting :some_setting, invalid_option: true, reader: true
89
- #
90
- # Would raise an ArgumentError as expected.
91
- #
92
- # However, the one case we can't catch here is when invalid options are supplied
93
- # without hash literal braces, but there are no other keyword arguments
94
- # supplied. In this case, a setting like:
95
- #
96
- # setting :hash_setting, my_hash: true
97
- #
98
- # Is parsed identically to the first case described above:
99
- #
100
- # setting :hash_setting, {my_hash: true}
101
- #
102
- # So in both of these cases, the default value will become `{my_hash: true}`. We
103
- # consider this unlikely to be a problem in practice, since users are not likely
104
- # to be providing invalid options to `setting` and expecting them to be ignored.
105
- # Additionally, the deprecation messages will make the new behavior obvious, and
106
- # encourage the users to upgrade their setting definitions.
107
-
108
- if Dry::Configurable.warn_on_setting_positional_default
109
- Dry::Core::Deprecations.announce(
110
- "default value as positional argument to settings",
111
- "Provide a `default:` keyword argument instead",
112
- tag: "dry-configurable",
113
- uplevel: 2
114
- )
115
- end
116
-
117
- options = {default: invalid_opts}
118
- end
119
-
120
- if block && !block.arity.zero?
121
- if Dry::Configurable.warn_on_setting_constructor_block
122
- Dry::Core::Deprecations.announce(
123
- "passing a constructor as a block",
124
- "Provide a `constructor:` keyword argument instead",
125
- tag: "dry-configurable",
126
- uplevel: 2
127
- )
128
- end
129
-
130
- options = options.merge(constructor: block)
131
- block = nil
132
- end
133
-
134
36
  ensure_valid_options(options)
135
37
 
38
+ options = {default: default, config_class: config_class, **options}
39
+
136
40
  node = [:setting, [name.to_sym, options]]
137
41
 
138
42
  if block
@@ -144,6 +48,14 @@ module Dry
144
48
  compiler.visit(ast.last)
145
49
  end
146
50
 
51
+ def config_class
52
+ options[:config_class]
53
+ end
54
+
55
+ def default
56
+ options[:default_undefined] ? Undefined : nil
57
+ end
58
+
147
59
  private
148
60
 
149
61
  def ensure_valid_options(options)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Configurable
5
+ class Extension < Module
6
+ # @api private
7
+ attr_reader :config_class
8
+
9
+ # @api private
10
+ attr_reader :default_undefined
11
+
12
+ # @api private
13
+ def initialize(config_class: Configurable::Config, default_undefined: false)
14
+ super()
15
+ @config_class = config_class
16
+ @default_undefined = default_undefined
17
+ freeze
18
+ end
19
+
20
+ # @api private
21
+ def extended(klass)
22
+ super
23
+ klass.extend(ClassMethods)
24
+ klass.instance_variable_set(:@__config_extension__, self)
25
+ end
26
+
27
+ # @api private
28
+ def included(klass)
29
+ raise AlreadyIncluded if klass.include?(InstanceMethods)
30
+
31
+ super
32
+
33
+ klass.class_eval do
34
+ extend(ClassMethods)
35
+ include(InstanceMethods)
36
+ prepend(Initializer)
37
+
38
+ class << self
39
+ undef :config
40
+ undef :configure
41
+ end
42
+ end
43
+
44
+ klass.instance_variable_set(:@__config_extension__, self)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -12,10 +12,7 @@ module Dry
12
12
  module Initializer
13
13
  # @api private
14
14
  def initialize(*)
15
- # Dup settings at time of initializing to ensure setting values are specific to
16
- # this instance. This does mean that any settings defined on the class _after_
17
- # initialization will not be available on the instance.
18
- @config = Config.new(self.class._settings.dup)
15
+ @config = self.class.__config_build__(self.class._settings)
19
16
 
20
17
  super
21
18
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable/errors"
4
-
5
3
  module Dry
6
4
  module Configurable
7
5
  # Common API for both classes and instances
@@ -4,18 +4,15 @@ require "set"
4
4
 
5
5
  require "dry/core/equalizer"
6
6
 
7
- require "dry/configurable/constants"
8
- require "dry/configurable/config"
9
-
10
7
  module Dry
11
8
  module Configurable
12
9
  # This class represents a setting and is used internally.
13
10
  #
14
11
  # @api private
15
12
  class Setting
16
- include Dry::Equalizer(:name, :value, :options, inspect: false)
13
+ include Dry::Equalizer(:name, :default, :constructor, :children, :options, inspect: false)
17
14
 
18
- OPTIONS = %i[input default reader constructor cloneable settings].freeze
15
+ OPTIONS = %i[default reader constructor cloneable settings config_class].freeze
19
16
 
20
17
  DEFAULT_CONSTRUCTOR = -> v { v }.freeze
21
18
 
@@ -25,105 +22,35 @@ module Dry
25
22
  attr_reader :name
26
23
 
27
24
  # @api private
28
- attr_reader :writer_name
25
+ attr_reader :default
29
26
 
30
27
  # @api private
31
- attr_reader :input
28
+ attr_reader :constructor
32
29
 
33
30
  # @api private
34
- attr_reader :default
31
+ attr_reader :children
35
32
 
36
33
  # @api private
37
34
  attr_reader :options
38
35
 
39
- # Specialized Setting which includes nested settings
40
- #
41
- # @api private
42
- class Nested < Setting
43
- CONSTRUCTOR = Config.method(:new)
44
-
45
- # @api private
46
- def pristine
47
- with(input: input.pristine)
48
- end
49
-
50
- # @api private
51
- def constructor
52
- CONSTRUCTOR
53
- end
54
- end
55
-
56
36
  # @api private
57
37
  def self.cloneable_value?(value)
58
38
  CLONEABLE_VALUE_TYPES.any? { |type| value.is_a?(type) }
59
39
  end
60
40
 
61
41
  # @api private
62
- def initialize(name, input: Undefined, default: Undefined, **options)
42
+ def initialize(
43
+ name,
44
+ default:,
45
+ constructor: DEFAULT_CONSTRUCTOR,
46
+ children: EMPTY_ARRAY,
47
+ **options
48
+ )
63
49
  @name = name
64
- @writer_name = :"#{name}="
65
- @options = options
66
-
67
- # Setting collections (see `Settings`) are shared between the configurable class
68
- # and its `config` object, so for cloneable individual settings, we duplicate
69
- # their _values_ as early as possible to ensure no impact from unintended mutation
70
- @input = input
71
50
  @default = default
72
- if cloneable?
73
- @input = input.dup
74
- @default = default.dup
75
- end
76
-
77
- evaluate if input_defined?
78
- end
79
-
80
- # @api private
81
- def input_defined?
82
- !input.equal?(Undefined)
83
- end
84
-
85
- # @api private
86
- def value
87
- return @value if evaluated?
88
-
89
- @value = constructor[Undefined.coalesce(input, default, nil)]
90
- end
91
- alias_method :evaluate, :value
92
- private :evaluate
93
-
94
- # @api private
95
- def evaluated?
96
- instance_variable_defined?(:@value)
97
- end
98
-
99
- # @api private
100
- def nested(settings)
101
- Nested.new(name, input: settings, **options)
102
- end
103
-
104
- # @api private
105
- def pristine
106
- with(input: Undefined)
107
- end
108
-
109
- # @api private
110
- def finalize!(freeze_values: false)
111
- if value.is_a?(Config)
112
- value.finalize!(freeze_values: freeze_values)
113
- elsif freeze_values
114
- value.freeze
115
- end
116
- freeze
117
- end
118
-
119
- # @api private
120
- def with(new_opts)
121
- self.class.new(name, input: input, default: default, **options, **new_opts)
122
- end
123
-
124
- # @api private
125
- def constructor
126
- options[:constructor] || DEFAULT_CONSTRUCTOR
51
+ @constructor = constructor
52
+ @children = children
53
+ @options = options
127
54
  end
128
55
 
129
56
  # @api private
@@ -131,36 +58,20 @@ module Dry
131
58
  options[:reader].equal?(true)
132
59
  end
133
60
 
134
- # @api private
135
- def writer?(meth)
136
- writer_name.equal?(meth)
137
- end
138
-
139
61
  # @api private
140
62
  def cloneable?
141
- if options.key?(:cloneable)
142
- # Return cloneable option if explicitly set
143
- options[:cloneable]
144
- else
145
- # Otherwise, infer cloneable from any of the input, default, or value
146
- Setting.cloneable_value?(input) || Setting.cloneable_value?(default) || (
147
- evaluated? && Setting.cloneable_value?(value)
148
- )
149
- end
63
+ children.any? || options.fetch(:cloneable) { Setting.cloneable_value?(default) }
150
64
  end
151
65
 
152
- private
153
-
154
66
  # @api private
155
- def initialize_copy(source)
156
- super
157
-
158
- @options = source.options.dup
67
+ def to_value
68
+ if children.any?
69
+ (options[:config_class] || Config).new(children)
70
+ else
71
+ value = default
72
+ value = constructor.(value) unless value.eql?(Undefined)
159
73
 
160
- if source.cloneable?
161
- @input = source.input.dup
162
- @default = source.default.dup
163
- @value = source.value.dup if source.evaluated?
74
+ cloneable? ? value.dup : value
164
75
  end
165
76
  end
166
77
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/map"
4
-
5
3
  require "dry/core/equalizer"
6
- require "dry/configurable/constants"
7
4
 
8
5
  module Dry
9
6
  module Configurable
@@ -11,27 +8,27 @@ module Dry
11
8
  #
12
9
  # @api private
13
10
  class Settings
14
- include Dry::Equalizer(:elements)
11
+ include Dry::Equalizer(:settings)
15
12
 
16
13
  include Enumerable
17
14
 
18
15
  # @api private
19
- attr_reader :elements
16
+ attr_reader :settings
20
17
 
21
18
  # @api private
22
- def initialize(elements = EMPTY_ARRAY)
23
- initialize_elements(elements)
19
+ def initialize(settings = EMPTY_ARRAY)
20
+ @settings = settings.each_with_object({}) { |s, m| m[s.name] = s }
24
21
  end
25
22
 
26
23
  # @api private
27
24
  def <<(setting)
28
- elements[setting.name] = setting
25
+ settings[setting.name] = setting
29
26
  self
30
27
  end
31
28
 
32
29
  # @api private
33
30
  def [](name)
34
- elements[name]
31
+ settings[name]
35
32
  end
36
33
 
37
34
  # @api private
@@ -41,37 +38,18 @@ module Dry
41
38
 
42
39
  # @api private
43
40
  def keys
44
- elements.keys
41
+ settings.keys
45
42
  end
46
43
 
47
44
  # @api private
48
45
  def each(&block)
49
- elements.values.each(&block)
50
- end
51
-
52
- # @api private
53
- def pristine
54
- self.class.new(map(&:pristine))
55
- end
56
-
57
- # @api private
58
- def finalize!(freeze_values: false)
59
- each { |element| element.finalize!(freeze_values: freeze_values) }
60
- freeze
46
+ settings.each_value(&block)
61
47
  end
62
48
 
63
49
  private
64
50
 
65
- # @api private
66
51
  def initialize_copy(source)
67
- initialize_elements(source.map(&:dup))
68
- end
69
-
70
- # @api private
71
- def initialize_elements(elements)
72
- @elements = elements.each_with_object(Concurrent::Map.new) { |s, m|
73
- m[s.name] = s
74
- }
52
+ @settings = source.settings.dup
75
53
  end
76
54
  end
77
55
  end
@@ -3,6 +3,6 @@
3
3
  module Dry
4
4
  module Configurable
5
5
  # @api public
6
- VERSION = "0.15.0"
6
+ VERSION = "0.16.0"
7
7
  end
8
8
  end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/array"
3
+ require "zeitwerk"
4
4
 
5
+ require "dry/core/constants"
5
6
  require "dry/configurable/constants"
6
- require "dry/configurable/class_methods"
7
- require "dry/configurable/instance_methods"
8
- require "dry/configurable/config"
9
- require "dry/configurable/setting"
10
7
  require "dry/configurable/errors"
11
8
 
12
9
  module Dry
10
+ # @api public
11
+ def self.Configurable(**options)
12
+ Configurable::Extension.new(**options)
13
+ end
14
+
13
15
  # A simple configuration mixin
14
16
  #
15
17
  # @example class-level configuration
@@ -44,27 +46,32 @@ module Dry
44
46
  #
45
47
  # @api public
46
48
  module Configurable
49
+ def self.loader
50
+ @loader ||= Zeitwerk::Loader.new.tap do |loader|
51
+ root = File.expand_path("..", __dir__)
52
+ loader.tag = "dry-configurable"
53
+ loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-configurable.rb")
54
+ loader.push_dir(root)
55
+ loader.ignore(
56
+ "#{root}/dry-configurable.rb",
57
+ "#{root}/dry/configurable/{constants,errors,version}.rb"
58
+ )
59
+ loader.inflector.inflect("dsl" => "DSL")
60
+ end
61
+ end
62
+
47
63
  # @api private
48
64
  def self.extended(klass)
49
65
  super
50
- klass.extend(ClassMethods)
66
+ klass.extend(Extension.new)
51
67
  end
52
68
 
53
69
  # @api private
54
70
  def self.included(klass)
55
- raise AlreadyIncluded if klass.include?(InstanceMethods)
56
-
57
71
  super
58
- klass.class_eval do
59
- extend(ClassMethods)
60
- include(InstanceMethods)
61
- prepend(Initializer)
62
-
63
- class << self
64
- undef :config
65
- undef :configure
66
- end
67
- end
72
+ klass.include(Extension.new)
68
73
  end
74
+
75
+ loader.setup
69
76
  end
70
77
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Holland
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
11
+ date: 2022-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: concurrent-ruby
14
+ name: dry-core
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: '0.6'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: '0.6'
27
27
  - !ruby/object:Gem::Dependency
28
- name: dry-core
28
+ name: zeitwerk
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.6'
33
+ version: '2.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.6'
40
+ version: '2.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -99,7 +99,7 @@ files:
99
99
  - lib/dry/configurable/constants.rb
100
100
  - lib/dry/configurable/dsl.rb
101
101
  - lib/dry/configurable/errors.rb
102
- - lib/dry/configurable/flags.rb
102
+ - lib/dry/configurable/extension.rb
103
103
  - lib/dry/configurable/instance_methods.rb
104
104
  - lib/dry/configurable/methods.rb
105
105
  - lib/dry/configurable/setting.rb
@@ -114,7 +114,7 @@ metadata:
114
114
  changelog_uri: https://github.com/dry-rb/dry-configurable/blob/main/CHANGELOG.md
115
115
  source_code_uri: https://github.com/dry-rb/dry-configurable
116
116
  bug_tracker_uri: https://github.com/dry-rb/dry-configurable/issues
117
- post_install_message:
117
+ post_install_message:
118
118
  rdoc_options: []
119
119
  require_paths:
120
120
  - lib
@@ -129,8 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 3.1.6
133
- signing_key:
132
+ rubygems_version: 3.3.7
133
+ signing_key:
134
134
  specification_version: 4
135
135
  summary: A mixin to add configuration functionality to your classes
136
136
  test_files: []
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/core/class_attributes"
4
-
5
- module Dry
6
- module Configurable
7
- extend Core::ClassAttributes
8
-
9
- # Set to false to suppress deprecation warning when a setting default is provided as a
10
- # positional argument
11
- defines :warn_on_setting_positional_default
12
- warn_on_setting_positional_default true
13
-
14
- # Set to false to suppress deprecation warning when a setting constructor is provided
15
- # as a block
16
- defines :warn_on_setting_constructor_block
17
- warn_on_setting_constructor_block true
18
- end
19
- end