-
-
Notifications
You must be signed in to change notification settings - Fork 56
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
Make Config#finalize!
finalize itself recurively and allow freezing values
#105
Conversation
64770f3
to
b25effd
Compare
config#finalize!
freeze itself recurivelyonfig#finalize!
freeze itself recurively
onfig#finalize!
freeze itself recurivelyConfig#finalize!
freeze itself recurively
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.
Thanks for tackling this! I left one comment. In general, we're trying to wrap up dry-configurable 1.0.0 and since it's used in a bunch of projects (+ we want to use it in hanami 2.0) we need to 100000% sure we're not breaking anything. Some behaviors are really tricky so we probably have to check your branch against dry-schema and dry-system too before merging.
lib/dry/configurable/setting.rb
Outdated
@@ -94,6 +96,12 @@ def pristine | |||
with(input: Undefined) | |||
end | |||
|
|||
# @api private | |||
def finalize! | |||
value.is_a?(Config) ? value.finalize! : value.freeze |
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.
I don't think that freezing values is the right way. You can't know in advance what values are, it can be anything. For instance, if it's a module or a class (a reasonable assumption for config!) then you won't be able to define new constants there. I don't this is expected.
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.
I thought that it would be questionable approach :)
My reasoning is that if we're finalizing config — it owns all the values and we should take that into account. IMHO
irb(main):005:0> App.config.database.dsn
=> "sqlite:memory"
irb(main):006:0> App.config.database.dsn << 'qwe'
=> "sqlite:memoryqwe"
is quite unexpected after we #finalize!
, especially since documentation says # Note that finalized configs cannot be changed.
& changelog says Introduce 'Configurable.finalize!' which freezes config and its dependencies
.
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.
Yeah, I understand the reasoning, I would be happy to have everything immutable in the first place. But my position on this it's not up to libraries to mutate objects provided by the user. Freezing is a type of mutation so this rule applies. There are two problems: 1) it's unexpected 2) you can't opt-out of this behavior. I don't think we should be unnecessarily smart about this. We could have a warning shown when a non-frozen value passed to the config but then I guess there'd be a ton of them in a typical project, I'm not sure if this would be helpful.
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.
Right now semantics of #finalize!
is not clearly defined, so what is the expected behavior? Freeze just config structure, so we're protected from deleting of used keys?
From the changelog entry I expected that dependencies
are config values, not sure what was meant by dependencies
if they're not.
Implicit mutation is certainly not a good thing, but #finalize!
is explicit user's decision and if semantic would be defined as freeze all the keys & values
— why not? If you want to opt-out — just don't call #finalize!
.
If we still need to freeze the structure without values — can we change #freeze
to freeze the config without values recursively, so the structure is set in stone, and make #finalize!
to freeze also the values?
Right now #finalize!
seems broken anyway, because freezing only top-level keys has doubtful usefullness.
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.
It's not that simple. finalized!
is called by dry-system and it's an essinatial part in the lifecycle, you can't skip it. When people install new version of dry-system witth current change they'll get new semantics for the "why not?" reason. I agree it shouldn't break things but the problem is we can't be sure and our semantics will be different from what's understood as a default in ruby: calling .freeze
on arrays and hashes doesn't freeze values.
WRT recursive finalize!
and freezing config structure I don't see problems with this. I agree it's not expected config structure can be changed after freezing.
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.
Re dry-system, I believe it no longer depends on this hook (in master) but yeah, if the behavior changes here it'll break current dry-system releases.
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.
Yeah, I checked, it's not called. It's not clear whether it should be though. All in all, I think we could have freezing values as an option .finalize!(freeze_values: true)
. We can debate if it should be true
or false
by default but either way, it provides an opt-in/opt-out mechanism which is good. WDYT?
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.
You're right that dry-system doesn't call finalize!
on its config at the moment. This would be worth investigating before we ship 1.0. I'll file an issue.
In fact, I'm not sure if we really finalize configs much at all across the dry-rb gems at the moment.
Having a freeze_values
option to opt in or out of freezing non-Config
values like a sensible way to go here 👍🏼
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.
Issue for dry-system here: dry-rb/dry-system#168
b25effd
to
1d8fc13
Compare
Should I just check dry-schema/dry-system locally or make draft PRs to them using this branch? |
1d8fc13
to
e6eef5b
Compare
Running locally should be enough. CI will later on run specs against dry-configuration master to double-check. |
lib/dry/configurable/setting.rb
Outdated
evaluate if input_defined? | ||
value if input_defined? |
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.
I can see why you've removed the private #evaluate
method here, to keep all the logic in #value
itself, but the net result is that this line here reads a lot worse – it's just a random noun hanging out there on its own line, rather than a verb describing the outcome of the method (which, in this case, is actually about a side effect, so a verb makes sense).
Is this just a change you just made along the way because you thought it would be nice, or is it critical to your work in this PR? I think I'd prefer to see it left unchanged and have this PR focus on the freezing behavior only.
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.
@value ||=
instead of evaluated?
leads to evaluating value during cloneable?
check from initialize_copy
which fails if finalize!(freeze_values: true)
was called. I don't quite like how it looks either, added alias evaluate -> value.
8ae0eb3
to
e32be04
Compare
Config#finalize!
freeze itself recurivelyConfig#finalize!
freeze itself recurively and allow freezing values
Sorry for delay, changed it to |
e32be04
to
ba2ff80
Compare
@@ -33,9 +33,7 @@ module InstanceMethods | |||
# Finalize the config and freeze the object | |||
# | |||
# @api public | |||
def finalize! | |||
return self if frozen? |
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.
#frozen?
checks would be unreliable now and I don't think that it's performance-critical part, just drop them.
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.
@ojab I wonder if this should just be idempotent or maybe if we should raise an error if a config is finalized and something tries to finalize it again 🤔
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.
Oh my, missed this comment. We can raise an error, but now it's idempotent, so didn't changed it.
IMHO raising an error would only lead to people figuring out how to workaround it and will not catch any real issues.
ba2ff80
to
6ba01a4
Compare
Rebased, anything to be done to get it merged? |
6ba01a4
to
a7f885d
Compare
Config#finalize!
freeze itself recurively and allow freezing valuesConfig#finalize!
finalize itself recurively and allow freezing values
a7f885d
to
14c1cdf
Compare
14c1cdf
to
e45d069
Compare
Gentle ping. |
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.
Would it be possible to just use freeze
method and remove finalize!
? Then Config#freeze
could do its thing and behave just like Hash#freeze
and THEN we could have Config#deep_freeze
to make things simpler and more explicit.
This would be more flexible and possibly future-proof given that there's a discussion about adding Object#deep_freeze
to Ruby.
I think getting this functionality done right is going to be crucial, that's why I'd prefer to really think it through. I should mention I'm integrating dry-configurable into rom-rb's core library and it's going to play major role there. So now all of a sudden I'm deeply interested in having proper config/settings/values freezing 😅 I used finalize!
there and of course it didn't do what I assumed it would.
umm, do you mean with recursion or without? Since it's not recursive/behaves like
Heh, maybe with ractors And underlying implementation probably wouldn't change (two separate recursive freezers would be more complicated, I guess), so feel free to merge it as is and make |
|
Oh, I'm not really accustomed to dry/rom ecosystem, with prior art it's perfectly logical naming without further justification.
part make me think that maybe we should break API and make it freeze values by default, since we're not 1.0 yet ._. (dunno if dry ecosystem follows semver (google says dry-rb/dry-rb.org#112 (comment), so API could be broken easily /shrug)). |
😿 |
1 similar comment
😿 |
We'll address this eventually. I'm just going back to OSS after a 3+ months break. |
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.
e45d069
to
765fb5b
Compare
@solnic @flash-gordon Final check: we all good to merge this? I'm good with it, and given my other recent work here, I can double check this in all our dry-rb projects and then roll it into a release sometime next week. |
765fb5b
to
b5126e6
Compare
@ojab Just noticed you pushed some changes, and I should have checked with you too, sorry! Are you OK with the current state of this PR? |
Yeah, I just rebased since there was a merge conflict and I noticed it after opening/reading your comment in GH UI, no worries. |
@timriley yeah I'm good with it though I don't remember all the details :) |
😿 |
@ojab I'll release this in the next few days! |
Released in 0.15.0: https://github.com/dry-rb/dry-configurable/releases/tag/v0.15.0 |
Fixes #104