-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Implement Object#with
#46681
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
Implement Object#with
#46681
Conversation
db7276f
to
b510e9f
Compare
Looking at the code, something like this is not currently supported? with(:@enabled => true)
yield
end Found many examples of this in the rails codebase itself. For example, rails/actionview/lib/action_view/base.rb Lines 273 to 290 in 59fe981
rails/actionpack/test/controller/flash_test.rb Lines 215 to 225 in 59fe981
|
Nope, and I don't wish to, similarly to why it only allow to use public methods, instance variables are out of scope. Otherwise we'd open a huge way to bypass encapsulation. |
What do you think about adding On one hand, |
Just to clarify, this is a very common pattern in test but not only. It isn't meant as purely a test helper, otherwise I think this is also commonly useful in production code, e.g. Rails.logger.with(level: Logger::Debug) do
# ...
end etc. |
Sure! I didn't mean to imply it was only for tests. I was just thinking of all the places it would be useful in Rails' own tests. But we could also add the |
Sad to see this "soft" rejected by Ruby itself (https://bugs.ruby-lang.org/issues/18951). Would it be possible to make it compatible with |
For |
Would it make sense to wait for https://bugs.ruby-lang.org/issues/18951 to be rejected (closed)? Do you think there is still any chance to unblock that? |
I don't think so. I tried several times already. |
🤔 If that's enough in tests and original description is also mostly test oriented, would it make sense to start with |
Again, I pointed to test code, but that's not what I want it for. |
Ahh, sorry. I have missed #46681 (comment). Than the last concern from my side would be the adoption of |
I disagree with that assessment. If you look at the detail of these methods the vast majority is internal APIs, or objects you'd never set attributes on. I also disagree with the comparison with |
Ohhh, I just wrote several of those damn ensure clauses for MRSK. Let's make this happen. Respect that matz don't want it in core, but I like both the naming and the placement for Active Support. |
Use case A very common pattern in Ruby, especially in testing is to save the value of an attribute, set a new value, and then restore the old value in an `ensure` clause. e.g. in unit tests ```ruby def test_something_when_enabled enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true # test things ensure SomeLibrary.enabled = enabled_was end ``` Or sometime in actual APIs: ```ruby def with_something_enabled enabled_was = @enabled @enabled = true yield ensure @enabled = enabled_was end ``` There is no inherent problem with this pattern, but it can be easy to make a mistake, for instance the unit test example: ```ruby def test_something_when_enabled some_call_that_may_raise enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true # test things ensure SomeLibrary.enabled = enabled_was end ``` In the above if `some_call_that_may_raise` actually raises, `SomeLibrary.enabled` is set back to `nil` rather than its original value. I've seen this mistake quite frequently. Object#with I think it would be very useful to have a method on Object to implement this pattern in a correct and easy to use way. NB: `public_send` is used because I don't think such method should be usable if the accessors are private. With usage: ```ruby def test_something_when_enabled SomeLibrary.with(enabled: true) do # test things end end ``` ```ruby GC.with(measure_total_time: true, auto_compact: false) do # do something end ``` Lots of tests in Rails's codebase could be simplified, e.g.: - Changing `Thread.report_on_exception`: https://github.com/rails/rails/blob/2d2fdc941e7497ca77f99ce5ad404b6e58f043ef/activerecord/test/cases/connection_pool_test.rb#L583-L595 - Changing a class attribute: https://github.com/rails/rails/blob/2d2fdc941e7497ca77f99ce5ad404b6e58f043ef/activerecord/test/cases/associations/belongs_to_associations_test.rb#L136-L150
b510e9f
to
1884323
Compare
Ok, I rebased and hardened the tests a bit. |
What about exceptions that could be raised and put the object in the intermediate state? What about thread-safety? That's what I had to do in climate_control https://github.com/thoughtbot/climate_control |
That's tested for: https://github.com/rails/rails/pull/46681/files#diff-07330a39026ef5856cdbc07bfeb738810459904fe6c663207b8dac9b96d97049R61-R73. If you think a case isn't covered, please open a PR.
I don't see what thread safety there is to account for here. If you use
I assume you mean: thoughtbot/climate_control@d556bf4, but I don't see what you're getting at. |
Should we consider a different name for this core extension method? Something longer like |
The method being defined in object doesn't prevent you in any way to define another method with the same name for your DSL. |
@byroot adding this to |
Sure, not a bad idea. |
Done in #47688 |
I strongly recommend renaming this to For example, any Additionally, @solnic mentioned, it doesn't make sense for |
This is totally irrelevant. Try this without active support loaded: Data.define(:foo).new(1).with(foo: 2) do
raise "Oops this block is useless"
end
Already changed. |
No one is going to try that without ActiveSupport loaded. But if they get used to using ActiveSupport’s |
That's an hypothetical I don't agree with, and there is similar redefinition of |
As far as I can tell, |
# | ||
# It can be used on any object as long as both the reader and writer methods | ||
# are public. | ||
def with(**attributes) |
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.
Tthis method name is super generic. Very easy to create confusion. Maybe call it #with_attributes
instead of just a generic #with
?
Use case
A very common pattern in Ruby, especially in testing is to save the value of an attribute, set a new value, and then restore the old value in an
ensure
clause.e.g. in unit tests
Or sometime in actual APIs:
There is no inherent problem with this pattern, but it can be easy to make a mistake, for instance the unit test example:
In the above if
some_call_that_may_raise
actually raises,SomeLibrary.enabled
is set back tonil
rather than its original value. I've seen this mistake quite frequently.Object#with
I think it would be very useful to have a method on Object to implement this pattern in a correct and easy to use way.
NB:
public_send
is used because I don't think such method should be usable if the accessors are private.With usage:
Lots of tests in Rails's codebase could be simplified, e.g.:
Thread.report_on_exception
:rails/activerecord/test/cases/connection_pool_test.rb
Lines 583 to 595 in 2d2fdc9
rails/activerecord/test/cases/associations/belongs_to_associations_test.rb
Lines 136 to 150 in 2d2fdc9