-
-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly support and deprecate default hash values provided as second positional argument to setting
for Ruby 2.6 and 2.7
#121
Conversation
@flash-gordon @solnic are you aware of any trickery that might let us get these arguments into the right local variables? |
A possible hack (but potentially good enough given this is a backwards compatibility shim and it's only problematic on older Rubies?): if One thing I should note here is that this is only a problem if there are no other options provided, so this case below is actually fine: setting :foo, {foo: "bar"}, reader: true In this case, the |
I'd go with this. I don't think bothering with warnings is worth it though |
@flash-gordon Thanks for the feedback :) Yeah, it felt reasonable to me too when I thought of it — I'll go ahead and prepare this, then! |
Had this problem a while back #14 |
e634e09
to
976c283
Compare
setting
for Ruby 2.6 and 2.7
@solnic @flash-gordon I've addressed this issue now and updated the PR description to describe my approach. Would you mind checking this out and letting me know if you're happy with it? Once we merge this in, then it means the change for the default value from the second positional argument to the I'll still do another couple of full rounds of testing inside the gems listed in #120, of course, but I hope that should be quite straightforward after this adjustment here. |
Thanks, @timriley, amazing work! I agree with the solution. When we have both an unknown key and a known key, we have to favor one of the two possible options: 1 - The user wants to use a known key on its default hash. At this point, known keys are:
So, probably, there're no big chances of name collision. With your solution, we're going with the second option, which is better because users will get an error because of unknown keyword arguments, which will help them work around it. Other than that, I think you'll need to remove those |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
lib/dry/configurable/dsl.rb
Outdated
options.each_with_object([{}, {}]) do |(key, val), (valid_options, invalid_options)| | ||
(Setting::OPTIONS.include?(key) ? valid_options : invalid_options)[key] = val | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
options.partition { |k, _| Setting::OPTIONS.include?(k) }.map(&:to_h)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So much neater, thanks @flash-gordon! I've applied this now.
This isn't a public method to the users of dry-configurable
976c283
to
66a0734
Compare
Thanks for the feedback, folks! Looks like we're all in agreement on approach here. I'm going to merge this now so I can get on with the testing. |
Before this change, if we defined a setting like this:
It would fail on Ruby 2.6 and 2.7 because inside
DSL#setting
, whose signature is this:The
default
value remains asUndefined
, andoptions
becomes{foo: "bar"}
, which then sees an exception raised inside#ensure_valid_options
because (of course) they are not valid options forsetting
.What we really want is for that hash provided as the second positional parameter to be assigned to
default
, andoptions
to remain as{}
.Per the extended code comments included in the change, we take the following approach to handle this case:
In Ruby 2.6 and 2.7, when a hash is given as the second positional argument (i.e. the hash is intended to be the setting's default value), and there are no other keyword arguments given, the hash is assigned to the
options
variable instead ofdefault
.For example, for this setting:
We'll have a
default
ofUndefined
and anoptions
of{my_hash: true}
If any additional keyword arguments are provided, e.g.:
Then we'll have a
default
of{my_hash: true}
and anoptions
of{reader: true}
, which is what we want.To work around that first case and ensure our (deprecated) backwards compatibility holds for Ruby 2.6 and 2.7, we extract all invalid options from
options
, and if there are no remaining valid options (i.e. if there were no keyword arguments given), then we can infer the invalid options to be a default hash value for the setting.This approach also preserves the behavior of raising an ArgumentError when a distinct hash is not intentionally provided as the second positional argument (i.e. it's not enclosed in braces), and instead invalid keyword arguments are given alongside valid ones. So this setting:
Would raise an ArgumentError as expected.
However, the one case we can't catch here is when invalid options are supplied without hash literal braces, but there are no other keyword arguments supplied. In this case, a setting like:
Is parsed identically to the first case described above:
So in both of these cases, the default value will become
{my_hash: true}
. We consider this unlikely to be a problem in practice, since users are not likely to be providing invalid options tosetting
and expecting them to be ignored. Additionally, the deprecation messages will make the new behavior obvious, and encourage the users to upgrade their setting definitions.