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
end
Let'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)
end
You 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
end
If 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
end
The 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
end
Object#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#with
to turn off a validation on a model object temporarily. -
Isolating the effects of a code change. For example, you could use
Object#with
to 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#with
to 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.