Incorporating remote: true into my update action in Rails

Today I’ll be refactoring a form inside my purchase history tracking app to incorporate Ajax. I recently added a feature that allows a user to create nicknames for products scraped from their shopping receipts, as the original names often are abbreviated and sometimes hard to decipher later on.

My nickname form is rendered as a partial on the product show view:

<%= render 'products/form', product: @product  %>

And the form:

<% if product.errors.any? %>
<h4><%= pluralize(product.errors.count, "error") %> prohibited this report from being saved:</h4>
<ul>
<% product.errors.each do |k, v| %>
<li><%= v %></li>
<% end %>
</ul>
<% end %>
<%= form_for product do |f| %>
<div class="well">
<form>
<div class="form-group">
<p><%= f.label "rename" %> <%=f.text_field :nickname, class: "form-control" %></p>
</div>
<%= f.submit :class => 'btn btn-primary' %>
</div>
</form>
<% end %>

The form connects to an update action in my products controller:

def update
@product.update(nickname: params[:product][:nickname])
if @product.save
redirect_to product_path(@product)
else
render :show
end
end

In addition to inside the form partial, the product nickname appears one other time on the product show view, at the top. I’ve already wrapped it in a span with the id #nickname in anticipation of the work ahead.

Under the current setup my product view page refreshes upon form submission, either through a re-render or as part of a redirect. But the only thing on the page getting updated is the one appearance of the product nickname at the top, so it makes sense to submit this form via Ajax. Rails makes the process easy with remote: true. First let’s refactor the first line of my form_for:

<%= form_for(product, remote: true) do |f| %>

This adds an extra attribute to our html form: data-remote=”true”. With that Rails will know to submit the form via Ajax. Now I need to refactor my controller to handle the request. The simplest way to do this is to just delete the html redirect: and the conditional logic surrounding it:

def update
@product.update(nickname: params[:product][:nickname])
end

No re-render or redirect is necessary anymore. All the extra work that we cut from the update action will now go into a new file: update.js.erb that we place into our views/products folder:

$('#error_explanation').html("");
<% if @product.valid? %>
$("#nickname").html("<%= @product.nickname %>");
<% else %>
<% @product.errors.full_messages.each do |message| %>
$('#error_explanation').append("<li> <%= escape_javascript(message) %> </li>");
<% end %>
<% end %>

In my form I remove the original validation errors div and associated logic, and replace it with just:

<div id="error_explanation"></div>

The code inupdate.js.erb file performs the work of the code I deleted from my form partial and controller update action. It first clears the #error-explanation div of any content, and then either updates the span with id #nickname, or populates the #error-explanation div with any form-validation errors that arise. Pretty nifty.