Git Product home page Git Product logo

dry-configurable's People

Contributors

actions-user avatar amhol avatar deepj avatar domon avatar dry-bot avatar emptyflask avatar flash-gordon avatar fryguy avatar gustavocaso avatar itsnikolay avatar joevandyk avatar koic avatar konnorrogers avatar landongrindheim avatar me avatar mrubens avatar nertzy avatar ojab avatar olleolleolle avatar qcam avatar sahilgoel1610 avatar saverio-kantox avatar skukx avatar solnic avatar spectator avatar swilgosz avatar timriley avatar v-kolesnikov avatar waiting-for-dev avatar zabolotnov87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dry-configurable's Issues

Can't add setting when config has been accessed

Way to reproduce:

class A
  extend Dry::Configurable

  setting :a
end

A.config

class A
  setting :b
end

# => Dry::Configurable::AlreadyDefinedConfig (Cannot add setting +b+, A is already configured)

If I omit the A.config in-between, there is no problem in adding setting :b.

I don't think it is desirable. In my scenario it happens because I'm developing a library with a bunch of extensions. Some of these extensions reopen a class to add a new setting. While surely it would be no problem in production (users will load all needed extensions at once, before any call to #config) it is a problem for my test suite, as I pick extensions depending on the test file and some tests access #config to make an assertion. I know I could remove the constant after each test, but anyway I don't think it is the desired behavior.

Callback "after configure"

I'm just throwing this idea out there, but it would be nice to have a "before configuration changed" callback triggered when configuration is about to be changed.
This would give us a hook to validate the configuration before accepting it.

0.13.0 release checklist

The upcoming 0.13.0 release will include a couple of large changes:

  1. Deprecating setting constructors provided via a block, favouring the constructor: argument instead
  2. Requiring default values be provided as a default: argument, rather than a second positional argument

While we've strived for these changes to be backwards compatible, they may cause breakage in certain circumstances.

To this end, we need to make sure we coordinate this 0.13.0 release of dry-configurable with all the gems that use it within the dry-rb, rom-rb, and Hanami ecosystems. This issue will serve as this coordination point.

Testing dependent gems

To make sure the new dry-configurable 0.13.0 release offers the appropriate level of backwards compatibility, we want to test all of its dependent gems across the dry-rb/rom-rb/hanami ecosystem.

The table below tracks the test we're (manually) running on each gem. The tests should exercise each of these cases:

  • The latest release of the gem, using dry-configurable 0.12.1, on Ruby 3.0
  • The latest release of the gem, using dry-configurable 0.12.1, on Ruby 2.7
  • The latest release of the gem, using dry-configurable 0.13.0 (master), on Ruby 3.0
  • The latest release of the gem, using dry-configurable 0.13.0 (master), on Ruby 2.7
  • The branch of the gem with dry-configurable 0.13.0 updates, using dry-configurable 0.13.0 (master), on Ruby 3.0
  • The branch of the gem with dry-configurable 0.13.0 updates, using dry-configurable 0.13.0 (master), on Ruby 2.7

The tests should pass in all cases.

Testing instructions are as follows:

  1. In your working copy, run git fetch --all --tags to get all tags
  2. Check out the the branch or tag for the test, e.g. git checkout tags/v0.8.0 or git checkout master
  3. Ensure the gemspec points to the appropriate version of dry-configurable for the test, e.g. 0.12.1 or the master branch
  4. Run the test suite and ensure it passes
Dependent gem dry-configurable 0.12.1 dry-configurable 0.13.0
dry-container @ v0.8.0 @ ruby 3.0
dry-container @ v0.8.0 @ ruby 2.7
dry-container @ master @ ruby 3.0 n/a
dry-container @ master @ ruby 2.7 n/a
dry-effects @ v0.1.5 @ ruby 3.0 ✅ (see note)
dry-effects @ v0.1.5 @ ruby 2.7
dry-effects @ master @ ruby 3.0 n/a
dry-effects @ master @ ruby 2.7 n/a
dry-monitor @ v0.4.0 @ ruby 3.0
dry-monitor @ v0.4.0 @ ruby 2.7
dry-monitor @ dry-rb/dry-monitor#43 @ ruby 3.0 n/a
dry-monitor @ dry-rb/dry-monitor#43 @ ruby 2.7 n/a
dry-rails @ v.0.3.0 @ ruby 3.0 ✅ (see note) ✅ (with dry-system release-0.18 branch)
dry-rails @ v.0.3.0 @ ruby 2.7 ✅ (with dry-system release-0.18 branch)
dry-rails @ dry-rb/dry-rails#44 @ ruby 3.0 n/a ✅ (with dry-system release-0.18 branch)
dry-rails @ dry-rb/dry-rails#44 @ ruby 2.7 n/a ✅ (with dry-system release-0.18 branch)
dry-schema @ v.1.7.0 @ ruby 3.0 n/a (see rows below, and original failure note)
dry-schema @ v.1.7.0 @ ruby 2.7 n/a n/a
dry-schema @ v.1.7.1 @ ruby 3.0
dry-schema @ v.1.7.1 @ ruby 2.7
dry-schema @ dry-rb/dry-schema#356 @ ruby 3.0 n/a
dry-schema @ dry-rb/dry-schema#356 @ ruby 2.7 n/a
dry-system @ 0.19.1 @ ruby 3.0 n/a (see rows below, and original failure note)
dry-system @ 0.19.1 @ ruby 2.7 n/a (see rows below) n/a
dry-system @ dry-rb/dry-system#186 (fix for 0.19.1) @ ruby 3.0
dry-system @ dry-rb/dry-system#186 (fix for 0.19.1) @ ruby 2.7
dry-system @ master @ ruby 3.0 n/a
dry-system @ master @ ruby 2.7 n/a
dry-validation @ v1.6.0 @ ruby 3.0 ✅ (see note)
dry-validation @ v1.6.0 @ ruby 2.7
dry-validation @ dry-rb/dry-validation#686 @ ruby 3.0 n/a
dry-validation @ dry-rb/dry-validation#686 @ ruby 2.7 n/a
hanami-view @ v2.0.0.alpha2 @ ruby 3.0 ✅ (with dry-system release-0.19 branch)
hanami-view @ v2.0.0.alpha2 @ ruby 2.7 ✅ (with dry-system release-0.19 branch)
hanami-view @ master @ ruby 3.0 n/a
hanami-view @ master @ ruby 2.7 n/a
hanami @ v2.0.0.alpha2 @ ruby 3.0 ✅ (with dry-system release-0.19 branch)
hanami @ v2.0.0.alpha2 @ ruby 2.7 ✅ (with dry-system release-0.19 branch)
hanami @ main @ ruby 3.0 n/a
hanami @ main @ ruby 2.7 n/a

Notes on failures

dry-effects @ v0.1.5 @ ruby 3.0

This has some unrelated failures at the moment due to breaking changes in dry-system (e.g. moving of the default_namespace setting). I temporarily fixed these by pinning to an older dry-system just for the tests.

dry-rails @ v.0.3.0 @ ruby 3.0

Specs here are failing because ActiveSupport's core extensions are not loaded and spec/dummy-6.x/dummy/config/environments/test.rb uses 1.hour, which results in this error:

An error occurred while loading spec_helper.
Failure/Error: "Cache-Control" => "public, max-age=#{1.hour.to_i}"

NoMethodError:
  undefined method `hour' for 1:Integer
# ./spec/dummy-6.x/dummy/config/environments/test.rb:21:in `block in <top (required)>'
# ./spec/dummy-6.x/dummy/config/environments/test.rb:8:in `<top (required)>'
# ./spec/dummy-6.x/dummy/config/environment.rb:7:in `<top (required)>'
# ./spec/spec_helper.rb:19:in `require'
# ./spec/spec_helper.rb:19:in `<top (required)>'

A quick fix is to chuck require "active_support/all" in spec_helper.rb, just above the require to the dummy app's config/environment.

This should be properly fixed in a separate PR.

dry-schema @ v.1.7.0 @ ruby 3.0 + dry-configurable 0.13.0

8 failing tests, all with the same error:

no implicit conversion of Dry::Configurable::Config into Hash

This is because I removed that implicit hash conversion in #114.

We'll need to update dry-schema to do explicit hash conversion and release that first.

dry-system @ 0.19.1 + dry-configurable 0.13.0

Now fixed in dry-rb/dry-system#186.

Original failure details:

This fails with the following error:

     Failure/Error: super(name, *args, &block)

     ArgumentError:
       wrong number of arguments (given 3, expected 1..2)
     # ./lib/dry/system/container.rb:105:in `setting'

The problem is with the params signature of the Dry::System::Container.setting override method, which I'm not entirely sure is even needed anymore. This will need a fix and a release to do more flexible argument forwarding before other gems depending on dry-system (i.e. hanami and dry-rails) can be compatible with the upcoming dry-configurable release.

A fix for this is already included in dry-rb/dry-system#179, which is merged into master, but alongside changes to use the latest dry-configurable setting API.

⚠️ We'll need to isolate a fix to the Dry::System::Container.setting override method (ideally something that has more flexible argument forwarding so that it works with both versions of dry-configurable) and release that as an independent patch release, separate from any of the other changes currently in dry-system master.

dry-validation @ v.1.6.0 @ ruby 3.0 + dry-configurable 0.12.1

The tests all pass apart from this flaky spec that has seen been disabled.

Updating dependent gems to use the new API

Here's a list of PRs to dependent gems to have them use the API.

n.b. the dry-validation and dry-schema PRs had some attempts at special compatibility shim code that should no longer needed now that dry-configurable 0.13.0 offers better backwards compatibility. These shims should be removed before testing.

Once these are all merged (and all the tests in the previous section are completed), we can consider a release.

Releasing the gems

This section still needs more work, but it'll be something like this

  1. Release dry-configurable 0.13.0
  2. For each of the gems above, make sure the PR to add the dry-configurable API changes is merged
  3. Bump their minimum dry-configurable version requirement to 0.13.0
  4. Cut a new minor release, e.g. x.[THIS ONE].y (?? - I think minor is better than patch because that way we're less likely to foist a whole bunch of deprecation notices on our users unsuspectingly)

Autoloading with zeitwerk results in uninitialized constant Dry::Core::ClassAttributes::IDENTITY

Describe the bug

Using the latest version of this gem, a simple require 'dry-configurable' results in the following stacktrace:

irb(main):002:0> require 'dry-configurable'
Traceback (most recent call last):
       16: from /usr/local/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
       15: from (irb):1
       14: from (irb):2:in `rescue in irb_binding'
       13: from (irb):2:in `require'
       12: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry-configurable.rb:3:in `<top (required)>'
       11: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry-configurable.rb:3:in `require'
       10: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable.rb:6:in `<top (required)>'
        9: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable.rb:6:in `require'
        8: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/class_methods.rb:6:in `<top (required)>'
        7: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/class_methods.rb:6:in `require'
        6: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/dsl.rb:4:in `<top (required)>'
        5: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/dsl.rb:4:in `require'
        4: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:5:in `<top (required)>'
        3: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:6:in `<module:Dry>'
        2: from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:11:in `<module:Configurable>'
        1: from /usr/local/bundle/gems/dry-core-0.9.0/lib/dry/core/class_attributes.rb:65:in `defines'
NameError (uninitialized constant Dry::Core::ClassAttributes::IDENTITY)

To Reproduce

Install the latest release of dry-configurable and require this gem.

Expected behavior

The gem would load fine and not throw any errors.

My environment

  • Affects my production application: YES
  • Ruby version: 2.7.6 + 3.1.2
  • OS: MacOS + ruby:2 Docker container

Additional information

When requiring dry-core before, everything works fine:

irb(main):001:0> require 'dry-configurable'
/usr/local/bundle/gems/dry-core-0.9.0/lib/dry/core/class_attributes.rb:65:in `defines': uninitialized constant Dry::Core::ClassAttributes::IDENTITY (NameError)

      def defines(*args, type: ::Object, coerce: IDENTITY) # rubocop:disable Metrics/PerceivedComplexity
                                                 ^^^^^^^^
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:11:in `<module:Configurable>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:6:in `<module:Dry>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/flags.rb:5:in `<top (required)>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/dsl.rb:4:in `require'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/dsl.rb:4:in `<top (required)>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/class_methods.rb:6:in `require'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable/class_methods.rb:6:in `<top (required)>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable.rb:6:in `require'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry/configurable.rb:6:in `<top (required)>'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry-configurable.rb:3:in `require'
	from /usr/local/bundle/gems/dry-configurable-0.13.0/lib/dry-configurable.rb:3:in `<top (required)>'
	from (irb):1:in `require'
	from (irb):1:in `<main>'
	from /usr/local/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
	from /usr/local/bin/irb:25:in `load'
	from /usr/local/bin/irb:25:in `<top (required)>'
	... 15 levels...

irb(main):002:0> require 'dry-core'
=> true
irb(main):003:0> require 'dry-configurable'
=> true

`finalize!` should freeze config recursively

Describe the bug

#finalize! freezes the main config, but internal configs are still not frozen.

To Reproduce

require 'dry-configurable'

class App
  extend Dry::Configurable

  setting :database do
    setting :dsn, 'sqlite:memory'
  end
end

App.config.finalize!

App.config.database.dsn = 'wrong!'
p App.config.database.dsn # => wrong!
App.config.database = 'wrong!' # => Cannot modify frozen config

Expected behavior

App.config is immutable, so even nested values can't be changed.

My environment

  • Affects my production application: Maybe?
  • Ruby version: 3.0.0
  • OS: linux x86_64
  • dry-configurable (0.12.1)

0.16.0 breaks Sinatra config_file

Describe the bug

After upgrading this library to 0.16.0 the config_file extension in Sinatra seems to break with

undefined method `foo' for Sinatra::Application:Class

when settings.foo gets called.

This was working just fine on 0.15.0.

I thought it might be zeitwerk, but 0.15.0 + zeitwerk work just fine so I suspect it's the changes in version 0.16.0 (possibly related to zeitwerk). Also this only seems to be happening in our tests in RSpec, not in normal dev environment.

To Reproduce

Install sinatra, sinatra-contrib and configure config_file extension. Let me know if you need a reproduction repository, I can prepare something later today or tomorrow.

Expected behavior

settings.foo from Sinatra should not crash.

My environment

  • Affects my production application: YES
  • Ruby version: 3.1.2
  • OS: docker image, alpine 3.15

Version 0.2.0 fails with dry-validation.

When using dry-validation 0.9.5 with dry-configurable 0.2.0 I get an error.
It happens when trying to require dry-validation.

I don't think I can provide any more inside but I can try to explain more if you want to.

*** is a path to spec file.
Here is the stacktrace:

bundler: failed to load command: rspec (/Users/x/.rbenv/versions/2.3.0/bin/rspec)
ArgumentError: unknown keywords: sanitizer, json, form
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-configurable-0.2.0/lib/dry/configurable.rb:83:in `setting'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema/class_interface.rb:25:in `<class:Schema>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema/class_interface.rb:6:in `<module:Validation>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema/class_interface.rb:5:in `<module:Dry>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema/class_interface.rb:4:in `<top (required)>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema.rb:17:in `require'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation/schema.rb:17:in `<top (required)>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation.rb:5:in `require'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry/validation.rb:5:in `<top (required)>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry-validation.rb:1:in `require'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-validation-0.9.5/lib/dry-validation.rb:1:in `<top (required)>'
 ***_spec.rb:2:in `require'
  ***_spec.rb:2:in `<top (required)>'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/configuration.rb:1435:in `load'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/configuration.rb:1435:in `block in load_spec_files'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/configuration.rb:1433:in `each'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/configuration.rb:1433:in `load_spec_files'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/runner.rb:100:in `setup'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/runner.rb:86:in `run'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/runner.rb:71:in `run'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/lib/rspec/core/runner.rb:45:in `invoke'
  /Users/x/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rspec-core-3.5.2/exe/rspec:4:in `<top (required)>'
  /Users/x/.rbenv/versions/2.3.0/bin/rspec:22:in `load'
  /Users/x/.rbenv/versions/2.3.0/bin/rspec:22:in `<top (required)>'

0.16.0 breaks Zeitwerk integration for other gems

Describe the bug

I'm not sure if this is exactly the problem of dry-configurable or karafka, but bumping the version from 0.15.0 to 0.16.x causes the issue.

I'm using dry-configurable and karafka 1.4x. After the update to 0.16.x, this is what I get when running bundle exec karafka server:

/Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/lib/karafka/server.rb:6:in `<class:Server>': uninitialized constant Concurrent::Array (NameError)

    @consumer_threads = Concurrent::Array.new
                                  ^^^^^^^
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/lib/karafka/server.rb:5:in `<module:Karafka>'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/lib/karafka/server.rb:3:in `<top (required)>'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/kernel.rb:27:in `require'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/kernel.rb:27:in `require'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader/helpers.rb:127:in `const_get'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader/helpers.rb:127:in `cget'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader.rb:239:in `block (2 levels) in eager_load'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader/helpers.rb:41:in `block in ls'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader/helpers.rb:27:in `each'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader/helpers.rb:27:in `ls'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader.rb:234:in `block in eager_load'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader.rb:219:in `synchronize'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/zeitwerk-2.6.1/lib/zeitwerk/loader.rb:219:in `eager_load'
  from <internal:kernel>:90:in `tap'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/lib/karafka.rb:70:in `<top (required)>'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/bin/karafka:3:in `require'
  from /Users/karol/.rvm/gems/ruby-3.1.2/gems/karafka-1.4.13/bin/karafka:3:in `<top (required)>'
  from /Users/karol/.rvm/gems/ruby-3.1.2/bin/karafka:25:in `load'
  from /Users/karol/.rvm/gems/ruby-3.1.2/bin/karafka:25:in `<main>'
  from /Users/karol/.rvm/gems/ruby-3.1.2/bin/ruby_executable_hooks:22:in `eval'
  from /Users/karol/.rvm/gems/ruby-3.1.2/bin/ruby_executable_hooks:22:in `<main>'

Based on the changelog, it looks like Zeitwerk has been recently introduced so probably it's related to that, but don't know yet what exactly went wrong that causes the issue for concurrent-ruby.

The existing settings will be clear when extend more than once mistakenly

In sub classes, if I extend Dry::Configurable again by mistake, the super class settings will be clear, such as the code following:

require 'dry-configurable'

# super class Daughter
class Daughter
  extend Dry::Configurable

  setting :name, 'Daughter'
  setting :sex, 'female'
  setting :age, 10
end

# sub class Cherry
class Cherry < Daughter
  setting :name, 'Cherry'
end

# sub class Kimi, extend again by mistake
class Kimi < Daughter
  extend Dry::Configurable
  setting :name, 'Kimi'
end

and the result in console, it will:

 >> Daughter.config.to_h
 => {:name=>"Daughter", :sex=>"female", :age=>10}

 >> Cherry.config.to_h
 => {:name=>"Cherry", :sex=>"female", :age=>10}

 >> Kimi.config.to_h
 => {:name=>"Kimi"}

the Kimi config, default settings 'sex', 'age' is cleared, and only name is valid.

the souce of self.extended is:

  module Configurable
    # @private
    def self.extended(base)
      base.class_eval do
        @_config_mutex = ::Mutex.new
        @_settings = ::Concurrent::Array.new
      end
    end
    ...
  end

Config#update does not return self

Describe the bug

Since upgrading to 0.11.3, config#update does not return self.

NoMethodError: undefined method `test' for {}:Hash

To Reproduce

require 'dry-configurable'

class App
  include Dry::Configurable
  setting :foo
end

app = App.new

config = app.config.update(foo: 'bar')
puts config.foo
# => undefined method `foo' for {:foo=>"bar"}:Hash

Expected behavior

Should return the config object as documented and as 0.8 did.

require 'dry-configurable'

class App
  include Dry::Configurable
  setting :foo
end

app = App.new

config = app.config.update(foo: 'bar')
puts config.foo
# => bar

Your environment

  • Affects my production application: No
  • Ruby version: 2.6.5
  • OS: Mac

to_h method for config

Would you be open to a PR to add a to_h method to config allowing:

App.configure do |config|
  config.database.dsn = 'jdbc:sqlite:memory'
end

App.config.to_h # => { dsn: 'jdbc:sqlite:memory' }

This would allow me to output all the configuration to the display without having to know all the keys.

By the way I could swear this used to be possible in an earlier version :)

EDIT: I think an earlier version might have used Struct which provided the to_h method.

Add `Settings#replace` to support wholesale replacement of settings in a configurable object

This would better support the use case in dry-rb/dry-system#162 (see dry-rb/dry-system#162 (comment) for specific notes), in which all the settings from one class are "imported" into another for use there too.

Currently, this is done like so:

AnotherConfigurableThing._settings.each do |setting|
  _settings << setting.dup
end

But it would be nicer to do it like this:

_settings.replace(AnotherConfigurableThing._settings.dup)

Values are being memoized between config instances

Describe the bug

This is a regression between versions 0.11.5 and 0.11.6. When you instantiate multiple instances of a class that include Dry::Configurable, the default values specified in the setting block are memoized between the instances.

I'm including a simplified reproduction script based on an issue with https://github.com/karafka/waterdrop/.

You can instantiate multiple WaterDrop consumers, each with different settings. WaterDrop has a default monitor (based on dry-monitor), which is instantiated by dry-configurable.

We subscribe to each consumer's monitor so that we can publish instrumentation metrics:

listener = KafkaStatsdListener.new
publisher.monitor.subscribe(listener)
publisher2.monitor.subscribe(listener)

This works fine with 0.11.5, but with 0.11.6 the messages from the monitor are duplicated.

To Reproduce

require 'dry-configurable'
require 'dry/configurable/version'

MyMonitor = Class.new

class Producer
  include Dry::Configurable

  setting(:monitor, false) { |monitor| monitor || MyMonitor.new }
end

producer1 = Producer.new
producer2 = Producer.new

puts "dry-configurable version: #{Dry::Configurable::VERSION}"
puts producer1.config.monitor.object_id == producer2.config.monitor.object_id

Output:

dry-configurable version: 0.11.6
true

Expected behavior

dry-configurable version: 0.11.5
false

Nested configs are not cloned, instead referenced in subclasses

I have tried to come out with a code to fix it, but I have been unable to.

This is the failing case:

RSpec.describe Dry::Configurable do
  context 'with nested configuration' do
    let(:base_klass) do
      Class.new do
        extend Dry::Configurable

        setting :nested do
          setting :thing, 'from base klass'
        end
      end
    end

    let(:klass) do
      Class.new(base_klass) do
        configure do |k|
          k.nested.thing = 'from klass'
        end
      end
    end

    let(:other_klass) do
      Class.new(base_klass) do
        configure do |k|
          k.nested.thing = 'from other klass'
        end
      end
    end

    it 'subclasses do not clobber each other' do
      expect(klass.config.nested.thing).to eq 'from klass'
      expect(other_klass.config.nested.thing).to eq 'from other klass'

      # the following fails, as it has been then changed by the configure block
      # of other_klass.
      expect(klass.config.nested.thing).to eq 'from klass'
    end

    it 'subclasses respect superclass own config' do
      expect(base_klass.config.nested.thing).to eq 'from base klass'
      expect(klass.config.nested.thing).to eq 'from klass'

      # the following fails, as it has been then changed by the configure block
      # of klass.
      expect(base_klass.config.nested.thing).to eq 'from base klass'
    end
  end
end

(added in gist https://gist.github.com/saverio-kantox/67d424acb56908bace5fd67f003d5910 too)

Basically the last subclass that has its nested thing touches, win, and all the others will get the same value.

Nested configs aren't finalized

Environment:

require 'dry-configurable'

class App
  extend Dry::Configurable

  setting :nested do
    setting :foo, 'bar'
  end

  setting :deeply_nested do
    setting :foo do
      setting :bar, 'baz'
    end
  end
end

Expected behaviour:

App.config.finalize! # or App.finalize!

App.config.nested.foo = 'oops'
# => Dry::Configurable::FrozenConfig: Cannot modify frozen config

App.config.deeply_nested.foo.bar = 'oops'
# => Dry::Configurable::FrozenConfig: Cannot modify frozen config

Right now, however, no errors are raised and we can easily modify the config.

Build a testing interface that doesn't require patching the test subject

RIght now, the suggested way to test configurable classes looks like this:

require "dry/configurable/test_interface"

# this is your module/class that extended by Dry::Configurable
module AwesomeModule
  enable_test_interface
end

before(:all) { AwesomeModule.reset_config }
# or 
before(:each) { AwesomeModule.reset_config }

I don't like this approach for several reasons:

  1. It requires me to add some code to my configurable class
  2. It makes my code less deterministic as it's different in every environment
  3. I just don't like using reset and before/after hooks

I suggest an interface that accepts a block, updates the config, then brings it back as it was.

Examples

around do |example|
  stub_configurable(described_class, value_one: 1, nested_value: { key: :value }) do 
    example.run
  end
end

If the param syntax is too complex, we can break it down to something like this:

around do |example|
  stub_configurable(described_class) do |configurable|
    configurable.config.value_one = 1
    configurable.config.nested_value.key = :value
    
    example.run
  end
end

or just like this:

around do |example|
  stub_configurable(described_class) do
    described_class.config.value_one = 1
    described_class.config.nested_value.key = :value
    
    example.run
  end
end

Resources

N/A

Understanding the thread safety claim - random sum every program invocation

Describe the bug

I'd like to understand a little bit more about the thread-safety claim, since a test program I wrote (inspired from https://www.yegor256.com/2018/11/06/ruby-threads.html) is returning a random sum on every program invocation, while without threads the result is stable.

Given this code:

class Program
  extend Dry::Configurable

  setting :counter

  def self.call; new.call; end

  def call
    self.class.config.counter = 1
    2.times.map do
      Thread.new do
        50.times do
          number_value = self.class.config.counter
          sleep(0.001)
          self.class.config.counter = self.class.config.counter + number_value
        end
      end
    end.each(&:join)
    self.class.config.counter
  end
end

To Reproduce

Program.call

Return value should be 1267650600228229401496703205376, which is what you get when you comment the Thread.new do and end.each(&:join) lines above, so no Threads are involved.

Expected behavior

Return is random:

Program.call
=> 927372692193078999176
Program.call
=> 1052337743009943038933
Program.call
=> 927372692193078999176
Program.call
=> 1052338402766319185501
Program.call
=> 1116795285174159114038

My environment

  • Affects my production application: NO
  • Ruby version: 2.7.2
  • OS: Mac OS X 10.15

`config.settings` regression

Describe the bug

When using setting :settings, reader: true I was previously able to access
the setting with:

config.settings.my_setting_name

Now it's:

config.settings[:settings].value.my_setting_name

To Reproduce

More information on Zulip: https://dry-rb.zulipchat.com/#narrow/stream/191662-general/topic/dry-system/near/188358017

Expected behavior

The previous behaviour.

Your environment

  • Affects my production application: Not yet. It would if it passed CI!
  • Ruby version: 2.6.5
  • OS: macOS

Option parser

Sometimes you may want to pre-process a value, ie:

class Foo
  extend Dry::Configurable

  setting :path { |value| Pathname(value) }
end

Unexpected Dry::Schema::MissingMessageError

Describe the bug

Dry validation combined with dry-configurable:0.13.0 raises a Dry::Schema::MissingMessageError exception when the validated attribute is an enum and the actual value isn't one of the listed enums.

This doesn't happen with versions < 0:.13, and although I can't be certain, it stands to reason that the issue is in this gem.

To Reproduce

# frozen_string_literal: true

gem 'dry-configurable', '< 0.13' # passes
# gem 'dry-configurable', '= 0.13.0' # fails

require 'dry-validation'
require 'rspec/autorun'

module Types
  include Dry::Types()
end

class Contract < Dry::Validation::Contract
  params do
    required(:arg).value(Types::Coercible::String.enum('foo', 'bar'))
  end
end

describe 'dry-validation' do
  let(:contract) { Contract.new }
  let(:validation) { contract.(arg: nil) } # or arg: 'xxx'
  subject(:error) { validation.errors.first }

  it 'reports validation errors as messages' do
    expect(error).not_to be_nil
    expect(error.path).to eq [:arg]
    expect(error.text).to eq 'must be one of: foo, bar'
  end
end

relevant error output

 Dry::Schema::MissingMessageError:
       Message template for :arg under "" was not found. Searched in:
       "en.dry_validation.errors.rules.arg.included_in?.arg."
       "en.dry_validation.errors.rules.arg.included_in?"
       "en.dry_validation.errors.included_in?.failure"
       "en.dry_validation.errors.included_in?.value.arg"
       "en.dry_validation.errors.included_in?.value.string.arg."
       "en.dry_validation.errors.included_in?.value.string"
       "en.dry_validation.errors.included_in?.arg."
       "en.dry_validation.errors.included_in?"

Expected behavior

No exception raised. See above.

My environment

Shouldn't be environment-specific, but anyhow:

  • Ruby version: 2.6.5

Misleading exception when `configure` is called on a frozen config

I'm getting a weird exception from dry-web/system with 0.7.0:

/Users/solnic/.gem/ruby/2.4.1/gems/dry-configurable-0.7.0/lib/dry/configurable.rb:167:in `raise_frozen_config': Cannot modify frozen config (Dry::Configurable::FrozenConfig)
        from /Users/solnic/.gem/ruby/2.4.1/gems/dry-configurable-0.7.0/lib/dry/configurable.rb:69:in `configure'
        from /Users/solnic/.gem/ruby/2.4.1/gems/dry-system-0.7.3/lib/dry/system/container.rb:99:in `configure'
        from /Users/solnic/.gem/ruby/2.4.1/gems/dry-web-0.7.1/lib/dry/web/container.rb:15:in `configure'

It doesn't modify any strings, it just calls configure for the second time.

Allow to set configuration based on conditions

Example:

class App
  extend Dry::Configurable

  setting :logger
  setting :production, if: -> { ENV['RACK_ENV'] == 'production' } do
	end
end

App.configure do |config|
  config.logger = { level: :debug }

  config.production do
    config.logger = { level: :info }
  end
end

Settings is freeze when the class method: config is called.

In some case, we need to call the 'config' method before setting some key-value pairs, such as following:

require 'dry-configurable'

class Daughter
  extend Dry::Configurable

  setting :name, 'Daughter'
  setting :events, {aa: [1, 2, 3], bb: [4, 5, 6], cc: [7, 8, 9]}
end

# define class methods from the config.events
module ActionsDefinition
  def self.included(base)
    base.class_eval {
      config.events.map { |event, params|
        define_singleton_method(event) {
          puts format('event: %{event}, params: %{params}', event: event, params: params)
        }
      }
    }
  end
end

class Eden < Daughter
  include ActionsDefinition
  setting :name, 'Eden'
end

the Eden config, set name to 'Eden' is invalid:

 >> Eden.config.to_h
 => {:name=>"Daughter", :events=>{:aa=>[1, 2, 3], :bb=>[4, 5, 6], :cc=>[7, 8, 9]}}

the souce of self.config is:

    def config
      return @_config if defined?(@_config)
      @_config_mutex.synchronize do
        @_config ||= ::Dry::Configurable::Config.create(_settings) unless _settings.empty?
      end
    end

it seems that, after the 'config' method is called, the settings is freezed, right?

including Dry::Configurable

require "dry-configurable"

class A
  include Dry::Configurable
end

A.new.setting :some_key, :some_value #=> NoMethodError: undefined method `<<' for nil:NilClass

Looks like it is unexpected behaviour

Accessing settings as strings via `#[]`

Describe the bug

Before the update to 0.11, a setting could be referenced via a string through config.[]. Now it must be a symbol, otherwise the setting is not found.

I'd like to know if it's desired behavior. If not, I can send a patch myself.

To Reproduce

class A
  extend Dry::Configurable

  setting :foo, :bar
end

A.config[:foo]
# => :bar
A.config["foo"]
# => ArgumentError (+foo+ is not a setting name)

Expected behavior

A.config["foo"]
# => :bar

Your environment

Affects two gems I maintain:

waiting-for-dev/warden-jwt_auth#21
waiting-for-dev/devise-jwt#159

Dry::Configurable.config fails on boot (wrong number of arguments for Config.new)

I've been working on extracting Dry::Types::Struct to its own project. I made this change to the gemspec so I could use Dry::Struct::VERSION:

diff --git i/dry-struct.gemspec w/dry-struct.gemspec
index a875711..038f209 100644
--- i/dry-struct.gemspec
+++ w/dry-struct.gemspec
@@ -1,5 +1,8 @@
 # coding: utf-8

+root = File.expand_path(__dir__)
+require File.join(root, 'lib/dry/struct/version.rb')
+
 Gem::Specification.new do |spec|
   spec.name          = 'dry-struct'
   spec.version       = '0.0.1'

This bundles fine but causes a strange issue when I run the tests:

$ bundle exec rspec
/Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-configurable-0.1.6/lib/dry/configurable.rb:53:in `initialize': wrong number of arguments (given 3, expected 0) (ArgumentError)
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-configurable-0.1.6/lib/dry/configurable.rb:53:in `new'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-configurable-0.1.6/lib/dry/configurable.rb:53:in `block in config'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-configurable-0.1.6/lib/dry/configurable.rb:51:in `synchronize'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-configurable-0.1.6/lib/dry/configurable.rb:51:in `config'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-container-0.3.4/lib/dry/container/mixin.rb:91:in `register'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-logic-0.3.0/lib/dry/logic/predicate_set.rb:9:in `predicate'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-logic-0.3.0/lib/dry/logic/predicates.rb:18:in `<module:Predicates>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-logic-0.3.0/lib/dry/logic/predicates.rb:9:in `<module:Logic>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-logic-0.3.0/lib/dry/logic/predicates.rb:8:in `<module:Dry>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-logic-0.3.0/lib/dry/logic/predicates.rb:7:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/constraints.rb:2:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/constraints.rb:2:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/constrained.rb:2:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/constrained.rb:2:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/builder.rb:51:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/builder.rb:51:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/definition.rb:1:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types/definition.rb:1:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types.rb:13:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry/types.rb:13:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry-types.rb:1:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-types-0.8.1/lib/dry-types.rb:1:in `<top (required)>'
    from /Users/johnbackus/Projects/dry-struct/lib/dry/struct.rb:1:in `require'
    from /Users/johnbackus/Projects/dry-struct/lib/dry/struct.rb:1:in `<top (required)>'
    from /Users/johnbackus/Projects/dry-struct/lib/dry-struct.rb:1:in `require'
    from /Users/johnbackus/Projects/dry-struct/lib/dry-struct.rb:1:in `<top (required)>'
    from /Users/johnbackus/Projects/dry-struct/spec/spec_helper.rb:14:in `require'
    from /Users/johnbackus/Projects/dry-struct/spec/spec_helper.rb:14:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration.rb:1295:in `require'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration.rb:1295:in `block in requires='
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration.rb:1295:in `each'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration.rb:1295:in `requires='
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration_options.rb:109:in `block in process_options_into'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration_options.rb:108:in `each'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration_options.rb:108:in `process_options_into'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/configuration_options.rb:21:in `configure'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/runner.rb:105:in `setup'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/runner.rb:92:in `run'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/runner.rb:78:in `run'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/lib/rspec/core/runner.rb:45:in `invoke'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.4.4/exe/rspec:4:in `<top (required)>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/bin/rspec:22:in `load'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/bin/rspec:22:in `<main>'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
    from /Users/johnbackus/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'

I haven't used dry-configurable before and I am only using it now because it is part of the code I am extracting from dry-types so sorry if this is a basic issue. I'm currently pretty stumped though since the version file itself is pretty simple:

module Dry
  class Struct
    VERSION = '0.0.1'.freeze
  end
end

The only place that dry-configurable is used is in the extracted errors module:

module Dry
  class Struct
    extend Dry::Configurable

    setting :namespace, self

    Error = Class.new(TypeError)

    class RepeatedAttributeError < ArgumentError
      def initialize(key)
        super("Attribute :#{key} has already been defined")
      end
    end
  end
end

Here is the dry-* in question:

$ cat Gemfile.lock | grep 'dry-'
    dry-struct (0.0.1)
      dry-configurable (~> 0.1)
      dry-equalizer (~> 0.2)
      dry-logic (~> 0.2, >= 0.2.3)
      dry-types (~> 0.8, >= 0.8.1)
    dry-configurable (0.1.6)
    dry-container (0.3.4)
      dry-configurable (~> 0.1, >= 0.1.3)
    dry-equalizer (0.2.0)
    dry-logic (0.3.0)
      dry-container (~> 0.2, >= 0.2.6)
      dry-equalizer (~> 0.2)
    dry-monads (0.0.2)
    dry-types (0.8.1)
      dry-configurable (~> 0.1)
      dry-container (~> 0.3)
      dry-equalizer (~> 0.2)
      dry-logic (~> 0.3, >= 0.3.0)
      dry-monads (>= 0.0.1)
  dry-struct!

I tried pointing to dry-configurable master but the issue remained.

Redefining class regression: undefined method `config' for class

Describe the bug

We use dry-configurable in our Rails project. In 0.9 we had no problems but since the upgrade to 0.11 we get the following error:

NameError: undefined method `config' for class `App'

After a little bit of digging I found out that it is only a problem the second time we define the class (That the class is redefined twice at all could be a Rails autoloading thing).

To Reproduce

require 'dry-configurable'

class App
  include Dry::Configurable
  setting :test
end
# => App

class App
  include Dry::Configurable
  setting :test
end
# => NameError: undefined method `config' for class `App'
# from .../ruby-2.6.5/gems/dry-configurable-0.11.3/lib/dry/configurable.rb:60:in `singleton class'

Expected behavior

(dry-configurable v0.9 behavior, also notice the different return value (nil instead of App))

require 'dry-configurable'

class App
  include Dry::Configurable
  setting :test
end
# => nil

class App
  include Dry::Configurable
  setting :test
end
# => nil

Your environment

  • We are in no hurry to upgrade the gem.
  • Ruby version: 2.6.5
  • OS: Mac/Linux

`v0.16.0` of dry-configurable breaks `require "dry-validation"`

Describe the bug

v0.16.0 release affects "dry-validation".

Please, check this GitHub Actions run:

Screenshot 2022-10-09 at 17 39 58

To Reproduce

Open irb for Ruby 3.1.

irb

Try to require "dry-validation".

require "dry-validation"

Check the following screenshot to see what happens:

Screenshot 2022-10-09 at 17 13 27

"dry-validation" version - v1.5.6.

Screenshot 2022-10-09 at 17 28 58

"dry-schema" version - v1.10.6.

Screenshot 2022-10-09 at 17 29 19

"dry-configurable" version - v0.16.0.

Screenshot 2022-10-09 at 17 29 36

Expected behavior

As it was in v0.15.0. A successful require.

My environment

  • Affects my production application: NO, but I am planning to release v0.1.0 of convenient_service (that may depend on dry-validation) at the beginning of November.
  • Ruby version: 3.1.
  • OS: Linux (GitHub Actions config, Docker config).

Reader option

I've found myself doing that a lot:

class Foo
  extend Dry::Configurable

  setting :something

  def self.something
    config.something
  end
end

WDYT about this:

class Foo
  extend Dry::Configurable

  setting :something, reader: true
end

Does not work from within a trap context

Describe the bug

dry-configurable does not work from a trap context

To Reproduce

Run the following script and press CTRL+C to stop it:

require 'dry/configurable'

class App
  extend Dry::Configurable

  setting :adapter, 'lol'
end

def trap_signal
  trap("INT") do
    p App.config.adapter
    exit
  end
end

trap_signal

sleep

Output:

^CTraceback (most recent call last):
  14: from run.rb:18:in `<main>'
  13: from run.rb:18:in `sleep'
  12: from run.rb:11:in `block in trap_signal'
  11: from dry-configurable-0.11.5/lib/dry/configurable/class_methods.rb:72:in `config'
  10: from dry-configurable-0.11.5/lib/dry/configurable/class_methods.rb:72:in `new'
   9: from dry-configurable-0.11.5/lib/dry/configurable/config.rb:26:in `initialize'
   8: from dry-configurable-0.11.5/lib/dry/configurable/config.rb:26:in `dup'
   7: from dry-configurable-0.11.5/lib/dry/configurable/config.rb:26:in `initialize_dup'
   6: from dry-configurable-0.11.5/lib/dry/configurable/settings.rb:61:in `initialize_copy'
   5: from dry-configurable-0.11.5/lib/dry/configurable/settings.rb:66:in `initialize_elements'
   4: from dry-configurable-0.11.5/lib/dry/configurable/settings.rb:66:in `each_with_object'
   3: from dry-configurable-0.11.5/lib/dry/configurable/settings.rb:66:in `each'
   2: from dry-configurable-0.11.5/lib/dry/configurable/settings.rb:67:in `block in initialize_elements'
   1: from concurrent-ruby-1.1.6/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb:18:in `[]='
concurrent-ruby-1.1.6/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb:18:in `synchronize': can't be called from trap context (ThreadError)

Expected behavior

The system should print the config value and exit

Your environment

  • Affects my production application: YES
  • Ruby version: 2.6.5 and 2.7.0
  • OS: Ubuntu

Bypass

Spawning a thread to bypass the trap context works but it's rather clumsy:

p Thread.new { App.config.adapter }.join.value

Reset configuration

I'm developing a gem that uses dry-configurable. In order to test it, I need to change some default settings in before hooks. I found that changed settings persist in subsequent tests. For that reason, I think I would need some reset method in order to apply it in after hooks.

What do you think about it? Would it be difficult to implement or does it have any drawback?

[Bug] Preprocessor are not used for initial nil value

(version 0.7.0)
Preprocessor is not used for initial nil value.

Example:

  • any_key - with initial nil value
  • another_key - with explicit default value
class A
  extend Dry::Configurable
  setting(:any_key) { |value| "empty value" }
  setting(:another_key, 25) { |value| "#{value} number" }
end

A.config.any_key # => nil # [BUG] Initial `nil` value, not preprocessed.
A.config.any_key # => nil # still not using a pre-processor
A.config.any_key = 25
A.config.any_key # => "empty value"
A.config.any_key = nil 
A.config.any_key # => "empty_value" (each next invocations uses a pre-processor after redifinition)
A.config.another_key # => "25 number"

Bug?

Reader pre-processor

We already have a writer pre-processor:

setting(:file) { |value| Pathname(value) }

But now, I'm in a use case where a reader pre-processor could also be useful. In my case I would like to throw an error if an important setting is not set. I think it would be nice to do it in the configuration layer, because it is its outermost boundary.

I see that having a setting with both writer and reader pre-processors could look a bit clumsy:

setting(:file,
        reader: ->(value) { value || raise('+file+ setting must be defined') }
       ) { |value| Pathname(value) }

If we are not in a "don't break backwards compatibility" phase, we could instead do:

setting(:file,
        reader: ->(value) { value || raise('+file+ setting must be defined') },
        writer: ->(value) { Pathname(value) })

Or, in order not to break compatibility, we could make a single block default to a writer pre-processor.

What do you think about it?

default values cannot be lazy evaluated

Describe the bug

Right now default: value is evaluated at the time of declaration, so object is always created. For karafka logger

setting :logger, ::Karafka::Instrumentation::Logger.new

this caused issues with initialization on RO filesystem (1, 2) even if non-default logger was passed.
In waterdrop it's kinda workarounded via

setting(:logger, false) { |logger| logger || Logger.new($stdout, level: Logger::WARN) }

but it's unnecessary complication and with real constructor would be even less readable.

To Reproduce

require 'dry-configurable'

RAISE_EXCEPTION = -> { raise }

class Config
  include Dry::Configurable

  setting :default_failure, default: RAISE_EXCEPTION.call
end

Expected behavior

Some API that would allow to skip default value initialization if non-default one is passed

require 'dry-configurable'

RAISE_EXCEPTION = -> { raise }

class Config
  include Dry::Configurable

  setting :default_failure, default: -> { RAISE_EXCEPTION.call }
end

Config.new(default_failure: 'does not fail')

My environment

  • Affects my production application: Kinda, PR to karafka was needed
  • Ruby version: 3.0.2
  • OS: linux

errors when using `setting :class`

require 'dry-configurable'

class Application
  extend Dry::Configurable
  setting :class, 'SimpleStrategy'
end

Application.config
Traceback (most recent call last):
	13: from tst.rb:11:in `<main>'
	12: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable.rb:148:in `config'
	11: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:57:in `define!'
	10: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:57:in `synchronize'
	 9: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:61:in `block in define!'
	 8: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:148:in `set_values!'
	 7: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/settings.rb:63:in `each'
	 6: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/settings.rb:63:in `each'
	 5: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/settings.rb:63:in `block in each'
	 4: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:155:in `block in set_values!'
	 3: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:57:in `define!'
	 2: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:57:in `synchronize'
	 1: from /home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:61:in `block in define!'
/home/jtzero/.asdf/installs/ruby/2.6.1/lib/ruby/gems/2.6.0/gems/dry-configurable-0.8.3/lib/dry/configurable/config.rb:148:in `set_values!': undefined method `settings' for nil:NilClass (NoMethodError)

Raise more meaningful exception when included more than once

When the extension is included in a class more than once, you end up with a confusing exception.

To Reproduce

Class.new.include(Dry::Configurable).include(Dry::Configurable)
NameError: undefined method `config' for class `#<Class:0x00007fa984607810>'

Expected behavior

A more meaningful exception should be raised explaining that you should not include the same extension more than once.

Refs #88

Array support?

I'm trying to declare a configuration with the following structure (in YAML notation)

...
endpoints:
  - url: "https://host1.domain.com"
    priority: 1
  - url: "https://host2.domain.com"
    priority: 2   
...

Am I missing something or it's not possible to declare a setting of an array type?

Something like this:

setting :endpoints do
  setting :url
  setting :priority
end

Thanks

Feature request — allow to easily reset configuration to original state

First of all, I wanted to thank the core crew for the fantastic work. I am really enjoying using most of the dry-rb gems in my most recent projects.

However, particularly with RSpec, and since the gem sets the <Module>.config at the module level, I was unable to find an easy way to reset my config back its original state at the beginning of each test. I don't have the defaults specified everywhere, so some fields should become nil, and I couldn't find an easy way of doing that.

Any chance you can either suggest how I go about submitting a PR, or perhaps you can suggest an existing solution?

Thanks!

Lazy loaded default parameters

Will there be a support for lazy loaded default options values? I have some settings that if not set, should be set from proc during runtime (with first invocation)

Apply configuration from existing hash

When you load configuration from, say, a YAML file, you already have a hash. It would be awesome to have a method that automatically recurs into the hash assigning configuration:

hash = YAML.load_file('config/database.yaml')
MyConfigurableClass.config.load_hash(hash)

0.16.0 breaking change to how settings with a `constructor` behave

Describe the bug

Sorry, I don't have the right terms to describe this clearly.

The value of a setting defined with a constructor was stable in 0.15.0. You could call your setting from anywhere in your application and get the same value. What constructor is for and how it behaves is not explained in the documentation, but my understanding from using it was that it would be called the first time you accessed the setting, to set the initial value or update the default value.

In 0.16.0, the constructor seems to be called every time you access the setting in your application.

To Reproduce

I have provided two scripts in this Gist. They are identical except for the dry-configurable version specified in the inline gemfile block.

It defines a dummy class called Something. Then it defines a module called App that extends Dry::Configurable and defines a setting named thing, with a constructor proc that creates a new Something. Then it calls App.thing twice and puts the object_id of each.

Run ruby dry-config-15.rb.

Then run ruby dry-config-16.rb.

Expected behavior

Without any mention of changed behavior in the CHANGELOG, I expect to get the same instance of the Something object when I call App.thing:

❯  ruby dry-config-15.rb
1160
1160

Instead I get 2 separate instances of the object when I call App.thing subsequently:

❯  ruby dry-config-16.rb
1060
1080

This side effect may be implied by "Improve memory usage by separating setting definitions from config values", but it's really not clear, and it breaks things even if you are not "accessing objects from _settings or the internals of Dry::Configurable::Config"

My environment

  • Affects my production application: YES (but fixed by pinning version to 0.15.0)
  • Ruby version: 3.1.0
  • OS: macOS BigSur

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.