To give a sense of how MVCoffee’s caching mechanism works, we’ll walk through a concrete example showing how all three parts of the framework work together.

Pure Rails

Suppose we’re working on an e-commerce site. The products we sell are called Items, and they are organized into Departments. In Rails we have two models:

class Item < ActiveRecord::Base
  belongs_to :department
end

class Department < ActiveRecord::Base
  has_many :items
end

In our Items controller, we have an index action that expects the Department to be supplied:

class ItemsController < ApplicationController
  before_action :set_department

  def index
    @items = @department.items
  end

  private
    def set_department
      @department = @mvcoffee.find Department, params[:department_id]
    end
end

Our Items index view might contain something like this typical Rails scaffolding:

<h1>Listing Items for Department <%= @department.name %></h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Sku</th>
      <th>Price</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody id="item_index_table">
    <%= render partial: 'item_table_row', collection: @items %>
  </tbody>
</table>

With the _item_table_row.html.erb partial looking like this:

    <tr>
      <td><%= item.name %></td>
      <td><%= item.sku %></td>
      <td><%= item.price %></td>
      <td><%= link_to 'Show', department_item_path(@item) %></td>
      <td><%= link_to 'Edit', edit_department_item_path(@item) %></td>
      <td><%= link_to 'Destroy', department_item_path(@item), method: "delete", data: { confirm: 'Are you sure?' } %></td>
    </tr>

It works well enough, but there are two problems:

With MVCoffee

Adding one line of code instructs the Items and Departments models that they will be cached on the client and to track the timestamp of modifications:

class Department < ActiveRecord::Base
  has_many :items

  caches_via_mvcoffee :items
end

The Departments model does need one extra field, items_updated_at add in a migration:

class AddItemsUpdatedAtToDepartments < ActiveRecord::Migration
  def change
    add_column :departments, :items_updated_at, :datetime    
  end
end

Now, in the server-side controller, instead of always fetching all Items per Department indiscriminately, we’ll only refresh if they are stale:

class ItemsController < ApplicationController
  before_action :set_department

  def index
    # !! This is the one different line !!
    # It will only go to the database if the client-side copy of this data is
    # older than the last update
    @items = @mvcoffee.refresh_has_many @department, :items
  end

  private
    def set_department
      @department = @mvcoffee.find Department, params[:department_id]
    end
end

Our client-side controller looks like this:

class MyNamespace.ItemIndexController extends MVCoffee.Controller
  onStart: ->
    # The @mvcoffee.refresh_has_many call on the server set the "department_id"
    # session value
    @department = MyNamespace.Department.find(@getSession("department_id")) 

  render: ->
    # This is querying the client-side cache.
    # At this point, we don't know or care if the department items were just 
    # pulled from the database for the first time during this execution of the 
    # application, whether they were just refreshed because they changed recently,
    # or whether they were fetched several minutes ago and have been sitting in the
    # in-memory cache.
    items = @department.items()

    @rerender
      selector: '#item_index_table'
      template: 'templates/item_index_row'
      collection: items
      as: 'item'
      locals:
        department: @department
  
  refresh: ->
    # This calls the index action on the server over Ajax, passing the age of the 
    # cache along with the request
    @fetch department_items_path(@department.id)

We have a JavaScript template that looks almost identical to our erb partial on the server, the only differences being the explicit parentheses and curly braces that are implied in Ruby:

      <tr>
        <td><%= item.name %></td>
        <td><%= item.sku %></td>
        <td><%= item.price %></td>
        <td><%= link_to('Show', department_item_path(item)) %></td>
        <td><%= link_to('Edit', edit_department_item_path(item)) %></td>
        <td><%= link_to('Destroy', department_item_path(item), { method: "delete", data: { confirm: 'Are you sure?' } } ) %></td>
      </tr>

That’s all it takes to make several things happen: