UpgradeRuby.com Logo

The go-to resource for upgrading Ruby, Rails, and your dependencies.

How to Fix The model: nil Error in form_with in Rails 8.0


Upgrading to Rails 8.0 introduces stricter validations around the form_with helper, which can cause unexpected ArgumentError exceptions when passing model: nil. This change eliminates silent failures and enforces explicit intent in your forms.

In this article, we’ll explore:

  • Why this error occurs in Rails 8.0 and how it differs from earlier versions.
  • Three practical approaches to resolve the issue during your upgrade.
  • Best practices for writing predictable, maintainable form code.

The Problem with Implicit Forms

Here’s the specific behavior by Rails version when model: nil is passed to form_with:

  • Rails 6.1–7.0: Accepted model: nil and treated it as a non-model form (equivalent to model: false). You were expected to also provide url:; otherwise, rendering would fail because non-model forms cannot infer an action URL.
  • Rails 7.1 (and later 7.x): Same behavior as above, but with a deprecation warning logged for model: nil. The guidance was to pass an actual model object, use model: false for a non-model form, or omit model: entirely and use url:.
  • Rails 8.0: The deprecated acceptance of model: nil was removed. Passing model: nil now raises an exception immediately.

For example, suppose you had a web application where users manage their profiles. Consider this common pattern for a user profile form:

<!-- app/views/users/edit.html.erb -->
<%= 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 call was treated as a non-model form (like model: false). If you also provided a url:, it rendered without raising; if you didn’t, rendering failed because form_with couldn’t infer the action URL for a non-model form. In Rails 7.1 you would also see a deprecation message in the logs.

In Rails 8.0, passing a nil model now raises an exception immediately:

ArgumentError: Passed nil to the :model argument, expect an object or false

Rather than accepting the call and falling back to non-model semantics, Rails alerts you to the problem immediately.

This change ensures developers explicitly define their form’s intent. Silent failures create difficult-to-debug issues in production. By raising an error when a model is nil, Rails 8.0 ensures forms target valid endpoints, preventing lost user data.

Three Approaches to Fixing the Error

There are three major approaches to resolving the model: nil error in form_with. Depending on your particular circumstances, one may be more useful than the others.

Approach 1: Initialize the Instance Variable in the Controller

Ensure the instance variable is properly initialized in the controller. This is the recommended approach for most situations. If the form creates a new record, initialize a new object rather than relying on nil.

For example, in your controller:

# app/controllers/users_controller.rb
def new
  @user = User.new
end

def edit
  @user = User.find(params[:id])
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 for new records, /users/:id for updates) and the HTTP method (POST or PATCH).

Approach 2: Provide a Fallback in the View

Provide a fallback object directly in the view using a boolean OR:

<!-- app/views/users/edit.html.erb -->
<%= form_with model: @user || User.new do |f| %>
  <%= f.text_field :name %>
  <%= f.submit "Save Profile" %>
<% end %>

This guarantees form_with receives a valid object, preventing exceptions. Ensure the controller logic aligns with the fallback behavior to avoid unintended create operations during updates.

This approach is particularly useful if you’re building shared partials that might receive a nil object under certain conditional rendering states. For example, you might render the same form partial from both the new and edit actions, and providing a fallback ensures the partial doesn’t break when the instance variable is unexpectedly nil.

Approach 3: Use URL-Based Forms

Stop using the model argument entirely for forms unrelated to ActiveRecord objects:

<!-- app/views/search/index.html.erb -->
<%= form_with url: search_path, scope: :query do |f| %>
  <%= f.text_field :keyword %>
  <%= f.submit "Search" %>
<% end %>

Explicitly declaring the url and scope bypasses the model requirement entirely. This clarifies the form’s intent as a generic submission rather than a resource mutation, aligning with Rails 8.0’s stricter validations.

:::tip In Rails, the form_with helper replaced form_for and form_tag. Choose model: for resource mutations or url: for generic submissions to match the helper’s intent.

For non-model forms where a partial expects a model: key, use model: false with an explicit url:. This maintains backward compatibility without triggering the Rails 8.0 error. :::

This approach is ideal for search forms, filtering interfaces, login forms, or any form that isn’t tied to a specific ActiveRecord resource.

Choosing the Right Approach

For most applications, initializing instance variables in the controller is ideal. This ensures controllers provide valid data to views, improving predictability and debuggability.

Shared partials expecting nil under specific conditions benefit from view fallbacks.

For non-ActiveRecord submissions like search or login forms, explicitly use url: to clarify intent.

Troubleshooting Common Scenarios

Scenario: nil from find_by

Controllers using find_by may return nil and trigger this error:

def edit
  @user = User.find_by(id: params[:id]) # Returns nil if not found
end

Handle nil explicitly:

def edit
  @user = User.find_by(id: params[:id])
  redirect_to users_path, alert: "User not found" if @user.nil?
end

Alternatively, use find, which raises ActiveRecord::RecordNotFound:

def edit
  @user = User.find(params[:id]) # Raises exception if not found
end

Handle the nil case explicitly:

def edit
  @user = User.find_by(id: params[:id])
  redirect_to users_path, alert: "User not found" if @user.nil?
end

Alternatively, use find to raise ActiveRecord::RecordNotFound:

def edit
  @user = User.find(params[:id]) # Raises exception if not found
end

Or use find instead, which raises ActiveRecord::RecordNotFound:

def edit
  @user = User.find(params[:id])  # Raises exception if not found
end

Scenario: Conditional Form Rendering

For conditional form rendering, always initialize the instance variable:

def new
  if some_condition
    @user = User.new(default_attributes)
  else
    @user = User.new
  end
end

Conclusion

Rails 8.0’s stricter validations enforce explicit form intent, reducing debugging time and improving reliability. While this change requires adjustments during upgrades, it encourages more maintainable code by enforcing predictable contracts.

You May Also Like