dry-configurable icon indicating copy to clipboard operation
dry-configurable copied to clipboard

Add convenience method Dry::Configurable::Settings#replace

Open skinnyjames opened this issue 3 years ago • 9 comments

Hiya, Really new to this project and dry-rb in general, so I don't expect to hit the mark on my first try, but this PR fixes #109

I'm curious if this is the desired api, or if it would be easier to copy settings from another Dry::Configurable class or object without accessing what I am assuming to be a an internal var?

I was thinking..

  class One
    extend Dry::Configurable
    setting :hello, "world"
  end

  class Two
    extend Dry::Configurable
    setting :hello, "bazz"
  end

  Two.clobber_config(One)
  
  One.config.hello #=> "World"
  Two.config.hello #=> "World"

skinnyjames avatar Jul 04 '21 13:07 skinnyjames

Thanks for working on this. It would be great to expose Settings object via #settings method, rather than this set object which is just some legacy we have (@timriley do you remember why it's even there?). Then you could just do things like ChildClass.settings.replace(ParentClass.settings) etc.

I'm working on integrating dry-configurable into rom-rb's core at the moment and methods like replace or merge or append etc. would be incredibly useful. I actually think some "settings and config algebra" should be a 1st-class feature in this library and it would make it really stand out. There are a lot of interesting usage patterns that involve merging/inheriting configuration that can actually be really tricky to deal with manually.

solnic avatar Jul 19 '21 06:07 solnic

No problem! I didn't realize that I was still failing checks. I'll get that cleaned up tonight. I can also work on merge / append too :)

dumb question: can I run rubocop locally to run the linter?

skinnyjames avatar Jul 19 '21 12:07 skinnyjames

@skinnyjames sure, bundle exec rubocop should do

flash-gordon avatar Jul 19 '21 14:07 flash-gordon

Fixed up the linting hopefully! I also added Settings#merge functionality in my origin branch https://github.com/skinnyjames-forks/dry-configurable/pull/1/files

If that seems like a good add, I'm happy to add it to the PR.

skinnyjames avatar Jul 20 '21 12:07 skinnyjames

It would be great to expose Settings object via #settings method, rather than this set object which is just some legacy we have (@timriley do you remember why it's even there?). Then you could just do things like ChildClass.settings.replace(ParentClass.settings) etc.

@solnic I don't have any clue why it's the way it is, unfortunately! I presume we've simply been preserving some very early API decision without giving it a whole ton of thought.

I agree that the set object would be better replaced by the full Settings instance — let's consider that for a follow up PR? Be good to do that pre-1.0.

I'll leave some other thoughts on this PR itself in another comment shortly 😄

timriley avatar Jul 22 '21 12:07 timriley

BTW, @skinnyjames, regarding your original comments in the PR description:

I'm curious if this is the desired api, or if it would be easier to copy settings from another Dry::Configurable class or object without accessing what I am assuming to be a an internal var?

I was thinking..

  class One
    extend Dry::Configurable
    setting :hello, "world"
  end

  class Two
    extend Dry::Configurable
    setting :hello, "bazz"
  end

  Two.clobber_config(One)
  
  One.config.hello #=> "World"
  Two.config.hello #=> "World"

I think the approach you chose in the actual code changes you've made is the right one. 👍🏼

Since Dry::Configurable can be mixed into all sorts of classes, I don't think we want to add too many extra methods to those classes. By sticking to setting defining new settings, and then settings (_settings right now, but I think we can rename it, per my comments to @solnic earlier), we can then allow most of the extra behaviour to hang off that settings object rather than polluting the method list of whatever class is mixing in our module.

timriley avatar Jul 22 '21 12:07 timriley

I'm noticing a weird state issue when invoking the config accessor before merging the settings. this passes.

        klass.setting :database do
          setting :dsn, "localhost"
        end
        other_klass.setting :database do
          setting :dsn, "remote"
        end

        other_klass._settings.merge!(klass._settings)
        expect(other_klass.config.database.dsn).to eql("localhost")

but this fails.

        klass.setting :database do
          setting :dsn, "localhost"
        end
        other_klass.setting :database do
          setting :dsn, "remote"
        end
        expect(other_klass.config.database.dsn).to eql("remote")
        other_klass._settings.merge!(klass._settings)
        expect(other_klass.config.database.dsn).to eql("localhost")

For now, I'm not at all sure why that is happening. adding failing tests in the meantime.

skinnyjames avatar Jul 23 '21 01:07 skinnyjames

Thanks @skinnyjames — just left you a couple more notes in the previous batch of comments. I'll take a look at the failing specs in the next day or two, I hope.

timriley avatar Jul 26 '21 13:07 timriley

Thanks @skinnyjames — just left you a couple more notes in the previous batch of comments. I'll take a look at the failing specs in the next day or two, I hope.

No problem. The api should be a little more clear now that "merge" returns settings, and "merge!" will merge those settings. I think the reason the tests will fail is because the config is cached once invoked. It seems intentional though.

skinnyjames avatar Jul 26 '21 13:07 skinnyjames