How to infer the form method for custom objects in Rails

Rails is able to automaticaly infer which HTTP verb to use when generating forms for Active Record models in form_with. How can we get the same for our plain old Ruby objects?

Problem

We want to avoid having to explicitly specify the method when rendering a form for our custom Cat object. Active Record objects don’t have to do this, so why do we?!

<%# don't want this explicit :method %>
<%= form_with model: @cat, method: :post do |f|%>

The persisted? method

The magic is all in the persisted? method. form_with will submit persisted objects via PATCH, while unpersisted objects get submitted via POST. We start by adding the method to our custom Cat object:

class Cat
  attr_accessor :name

  def persisted?
    false # or add fancier logic here
  end
end

Now in the view we can pass the model and the form will do the right thing, just like an Active Record model would. Problem solved!

<%= form_with model: @cat do |f| %>

In the wild

Relying on persisted? is a form of polymorphism. We can pass different objects into the same form and it will correctly pick the right HTTP verb. One of the most common use-cases in the wild is sharing a single _form.html.erb partial for both the new and create cases as seen in the classic Rails scaffolding.

ActiveModel compatibility

persisted? is part of ActiveModel. You get it for free if you include ActiveModel::API into your object (which defines persisted as false). As demonstrated above, you can also define the method yourself without including the module..

Writing your custom Ruby objects to be ActiveModel-compatible, whether or not you include any of the actual modules, allows them to integrate natively with Rails’ controller and view helpers. This gives you an experience that’s just as smooth as working with regular Active Record objects.