How to Fix the model: nil Error in form_with in Rails 8.0
In the mid-19th century, before the introduction of standardized addressing, sending a letter through the United States Postal Service was a remarkably permissive affair. One could send a letter addressed to “John Smith, near the old oak tree, Springfield,” and the local postmaster would often figure it out. It was forgiving, but as the volume of mail grew, this permissive behavior became a liability. Letters would fail to arrive without any notice, or be delivered to the wrong person entirely. To fix this, in 1963, the postal service introduced strict addressing requirements and ZIP codes. It required more explicit intent from the sender, but it eliminated the silent, unpredictable failures.
Upgrading a Ruby on Rails application often follows a similar trajectory. As the framework matures, it sheds permissive behaviors in favor of stricter, more predictable contracts. For example, when upgrading to Rails 8.0, we often encounter a frustrating exception in the view layer: an ArgumentError indicating model: nil when using the form_with helper.
Before we get into that, though, let’s take a step back and understand why this error suddenly appears after a Rails 8.0 upgrade, and why the framework’s designers decided to enforce this stricter behavior.
The Problem with Implicit Forms
In earlier versions of Rails, the form_with helper, strictly speaking, was surprisingly forgiving. If we passed a nil object to the model: argument — often because an instance variable failed to load or wasn’t initialized in the controller — Rails would sometimes swallow the issue. It might generate a form with an empty action, or fall back to the current page’s URL.
For example, if you had a Web application to which your users uploaded their profiles, consider this common pattern for a user profile form:
<%= form_with model: @user do |f| %>
<%= f.text_field :name %>
<%= f.submit "Save Profile" %>
<% end %>
If @user evaluated to nil in Rails 7.0 or earlier, the form might still render, silently masking a bug in the controller. However, in Rails 8.0, the framework is stricter about its form builder arguments. Passing a nil model now raises an exception, generating an error in the logs that looks something like this:
ActionView::Template::Error (model: nil provided to form_with):
Rather than silently rendering an invalid or empty form, Rails alerts you to the problem immediately.
One may wonder: why did Rails 8.0 change this behavior? The answer is straightforward. This change is significant because it forces developers to be explicit about their form’s intent. Silent failures lead to difficult-to-debug issues in production. By raising an error when a model is nil, Rails 8.0 ensures that your form has a valid target for its submission, preventing user data from being submitted to nowhere.
Three Approaches to Fixing the Error
There are three major approaches to resolving the model: nil error in form_with; depending on the particular circumstances you find yourself in, one of them may be more useful than the other two.
The first is to ensure the instance variable is properly initialized in your controller; this is my preferred method. If the form is meant to create a new record, the instance variable should not be nil. Instead, we should instantiate a new, empty object.
For example, in your controller:
# app/controllers/users_controller.rb
def new
@user = User.new
end
This approach provides the form builder with the correct class, allowing Rails to infer the URL, scope, and routing automatically. When the form renders, form_with knows exactly how to construct the action URL (e.g., /users) and the HTTP method (POST).
The second is to provide a fallback object directly in the view using a boolean OR.
<%= form_with model: @user || User.new do |f| %>
<%= f.text_field :name %>
<%= f.submit "Save Profile" %>
<% end %>
You also may notice that this technique keeps the controller light, as form_with is guaranteed to receive a valid object, thus preventing the exception. However, we must be careful that this matches the controller’s expectations; if the controller expects an update operation but receives a create operation, it could lead to unexpected behavior.
The third option is to stop using the model argument entirely if the form is not actually tied to an ActiveRecord object.
<%= form_with url: search_path, scope: :query do |f| %>
<%= f.text_field :keyword %>
<%= f.submit "Search" %>
<% end %>
By explicitly declaring the url and scope, we bypass the model requirement entirely. This clarifies the intent of the form — it’s a generic submission, not a resource mutation — and aligns perfectly with the tighter validation in Rails 8.0.
Tip: In Rails, the
form_withhelper replaced bothform_forandform_tag. Understanding whether your form is mutating a resource (usingmodel:) or submitting generic data (usingurl:) is central to picking the correct approach.
Generally speaking, the first option is simpler. The second option, though, will often make more sense if you are building shared partials that might receive a nil object under certain conditional rendering states. The third option is ideal when building a search form or a custom filtering interface.
Conclusion
Of course, fixing this particular form_with error is only part of the battle when upgrading an application. Upgrading to Rails 8.0 requires us to be more explicit with our form builders, shedding the permissive behaviors of the past in favor of strict, predictable contracts.
While encountering the model: nil error can be a stumbling block during an upgrade, it ultimately leads to more robust and predictable view code. By ensuring our instance variables are properly initialized, or by explicitly defining URLs for non-model forms, we can resolve this error and improve our application’s overall reliability.
Sponsored by Durable Programming
Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.
Hire Durable Programming