This page shows how MVCoffee makes it easy to perform Rails-style validations on input forms.

Pure Rails

Suppose we’re working on an e-commerce site. The products we sell are called Items, and they have a name, a price, and a quantity on hand. In Rails we have this model:

class Item < ActiveRecord::Base
  validates :name, presence: true
  validates :price, 
    numericality: { greater_than_or_equal_to: 0 }
  validates :quantity_on_hand, 
    numericality: { only_integer: true, greater_than_or_equal_to: 0 }
end

In the views for Items, we have this partial to render the input form:

<%= form_for(@item, html: {id: "item_form"}) do |f| %>
  <ul id="item_form_errors">
  </ul>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :price %><br>
    <%= f.text_field :price %>
  </div>
  <div class="field">
    <%= f.label :quantity_on_hand %><br>
    <%= f.number_field :quantity_on_hand %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

The standard method for handling the form in the controller (using Rails scaffolding) looks like this, with a lot of special case handling for html or json:

class ItemsController < ApplicationController

  def create
    @item = Item.new(item_params)

    respond_to do |format|
      if @item.save
        format.html { redirect_to @item, notice: 'Item was successfully created.' }
        format.json { render :show, status: :created, location: @item }
      else
        format.html { render :new }
        format.json { render json: @item.errors, status: :unprocessable_entity }
      end
    end
  end

end

As it stands, if the user has not entered valid information into the form, a full round trip to the server is performed and the server performs validation. This taxes your server, making it harder to scale, and makes for a sluggish user experience.

Of course, we can do some validation on the client with jQuery:

$( document ).ready(function() {
  $("#item_form").submit(function(eventObject) {
    var $errors_list;
    var item_name;
    var valid = true;
  
    $errors_list = $("#item_form_errors");
    $errors_list.empty();
  
    item_name = $("#item_name").val();
    if (/\S+/.test(val)) {
      $errors_list.append("<li>Item name can't be blank</li>");
      valid = false;
    }
    
    ...
  });
});

But this quickly becomes tedious. We’re reinventing the validation wheel over and over, intercepting form submissions over and over. Not exactly DRY. Plus, if we try to handle the form submission over Ajax now, the controller’s wishes that we redirect after success will not be honored. And finally, since we’re hand-baking the validations in jQuery, there is a risk that the validations won’t match. We could fail on the client on a validation that would pass on the server. Or vice versa, we could let things that should fail pass through for a poor user experience.

With MVCoffee

With MVCoffee, we’ll leave the server-side validation on the model in place, as it is ultimately the gate keeper of the data. But we can make an MVCoffee Model that matches the Rails validations exactly:

it = class MyNamespace.Item extends MVCoffee.Model

it.validates "name", test: "presence"
it.validates "price",
  test: "numericality"
  greater_than_or_equal_to: 0
it.validates "quantity_on_hand",
  test: "numericality"
  only_integer: true
  greater_than_or_equal_to: 0

Now, all we have to do is attach this Model to the input form with what’s called a “clientize customization” in our MVCoffee Controller:

class MyNamespace.EditItemController extends MVCoffee.Controller
  onStart: ->
    @item = new MyNamespace.Item
    @addClientizeCustomization    
      selector: "#item_form"
      model: @item
  
  item_form_errors: (errors) ->
    $errors_list = $("#item_form_errors")
    $errors_list.empty()
    for error in errors
      $errors_list.append "<li>" + error + "</li>"

That’s it. Now the validations are performed on the client, will protect against an unnecessary round trip, and are guaranteed to match the Rails validations. The form will be submitted over Ajax if validation passes, giving you the option to provide feedback on that page, or redirect away upon success.

Which leaves one loose end. Our Rails controller can be much simpler now:

class ItemsController < ApplicationController

  def create
    @item = Item.new(item_params)

    if @item.save
      redirect_to item_path(@item.id), notice: 'Item was successfully created.'
    else
      render :new, errors: @item.errors
    end
  end

end

With MVCoffee, we no longer need to distinguish between html and json in the Rails controller and can clean up all that respond_to do |format| business. In the case of a json request over Ajax, the call to redirect_to will add a redirect directive in the JSON served to the client, and the MVCoffee Runtime will perform the redirect client-side. Additionally, the Runtime uses Turbolinks to perform the redirect, so you’ll see improved performance and a smoother user experience as well.