dry-configurable 0.15.0 → 0.16.0

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