Rails 7.1 adds Object#with method

railsOctober 24, 2023Dotby Alkesh Ghorpade

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.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right