Rails 7.1 adds Object#with method
Rails 7.1 adds Object#with method.
The with method is a short
and
alternate option for the begin..ensure clause.
It can be used on any object as long as both the reader
and
writer methods are public.
Before Rails 7.1
A typical pattern in Ruby,
especially when working with tests,
is to save the old value of an attribute,
set a new value,
and
then restore the old value in the ensure clause.
def test_with_new_value
old_value = APILibrary.enabled?
APILibrary.enable!
# testing steps
ensure
APILibrary.enabled = old_value
endLet's say you have a Rails application with a feature flag for displaying a new dashboard to your users. It is currently disabled on production, but you need to enable the feature flag for testing purposes. You should implement the below steps for testing the new dashboard.
def test_user_can_access_new_dashboard
Flipper.enable(:new_dashboard)
# testing steps
ensure
Flipper.disable(:new_dashboard)
endYou need to make sure that the ensure block is added
every time an attribute is modified.
Developers can easily make mistakes
and
must remember to add the ensure block.
In a few scenarios,
an exception can change the value of the attribute to nil.
def test_with_new_value
method_call_that_raises_error
old_value = APILibrary.enabled?
...
ensure
APILibrary.enable = old_value
endIf the method_call_that_raises_error raises an error,
the APILibrary.enable will be set to nil.
In Rails 7.1
With the changes in Rails 7.1,
you can use the with method to change the attribute values within a block.
You are no longer required to add the ensure clause,
as the with method will take care of these things.
To use Object#with,
you pass in a hash of attributes to set
and
a block.
The block will be executed with the attributes set,
and
then the attributes will be restored to their original values
when it exits.
The examples above can be changed to replace ensure by
Object#with method.
def test_with_new_value
APILibrary.with(enable: true) do
# add code here
end
endThe flipper flag code will be modified as below:
def test_user_can_access_new_dashboard
Flipper.with(enable: :new_dashboard) do
# add code here
end
endObject#with can be useful for a variety of purposes,
such as:
-
You are temporarily changing the state of an object. For example, you could use
Object#withto turn off a validation on a model object temporarily. -
Isolating the effects of a code change. For example, you could use
Object#withto wrap a block of code that changes an object's state so that you can quickly revert the changes if something goes wrong. -
Simplifying complex code. For example, you could use
Object#withto refactor a series of method calls that are changing the state of an object into a single block.
Note:
The with method is unavailable on nil,
true/false,
Integer
and
Float objects.
To know more about this feature, please refer to this PR.