Jun 30 2008

Abstract resource

A large portion of the internet is governed by HTTP and the World Wide Web in particular is designed based on the REST architectural style. It makes sense to design web applications or web based services in a way that respects and harnesses the web's underlying architecture.

When it comes to developing web applications, Model-View-Controller (MVC) is one of the dominant architectural patterns current web frameworks are based on. MVC is not restricted to building web apps, on the contrary, its history can be traced back to 1979 and Smalltalk and has been originally applied to the development of applications which involved user interfaces.

The majority of Ruby web frameworks, especially the ones inspired by Rails, employ MVC and offer some sort of support for REST style application development, typically by defining resources which can be accessed through a URI and manipulated by making use of standard HTTP methods such as GET, PUT, POST, DELETE.

The above unveils an obvious similarity between the way HTTP resources can be manipulated - the four verbs can fundamentally constitute CRUD operations - and another common tier in web applications nowadays, databases.

web-db

Controllers in Merb, Rails or other similar Ruby, or not, web frameworks are a busy abstraction. A controller typically needs to dispatch to relevant actions, consolidate HTTP payloads, deal with sessions, sometimes caching, etc. These controllers are usually REST aware, meaning that they will by default map routed URI HTTP operations to a standard set of actions, namely index, show, create, edit, update, destroy.

If we focus on our application exposing strictly REST resource based interfaces, and assume that these resources directly map to the application's database schema, we can relieve controllers from some of the associated strain by abstracting away the discussed common functionality.

module CrudTemplate
  def resource
    raise "You must define a resource"
  end

  def index
    instance_variable_set(resource_sym_plural, resource.find(:all))
    render
  end

  def show
    assign_resource(resource.find(params[:id]))
    render
  end

  alias edit show
  alias delete show

  def new
    assign_resource(resource.new(resource_attrs))
    render
  end

  def create
    r = resource.new(resource_attrs)
    assign_resource(r)
    if r.save
      on_create_success(r)
    else
      on_create_failure(r)
    end
  end

  def on_create_success(r)
    redirect(resource_sym)
  end

  alias on_update_success on_create_success

  def on_create_failure(r)
    assign_resource(r)
    render(:new, :status => 400)
  end

  def update
    r = resource.find(params[:id])
    if r.update_attributes(resource_attrs)
      on_update_success(r)
    else
      on_update_failure(r)
    end
  end

  def on_update_failure(r)
    assign_resource(r)
    render(:edit)
  end

  def destroy
    if resource.destroy(params[:id])
      on_destroy_success(r)
    else
      on_destroy_failure(r)
    end
    redirect(resource_sym)
  end

  def self.included(controller)
    controller.show_action(*shown_actions)
  end

  protected

  def resource_attrs
    {}
  end

  def self.shown_actions
    [:index, :show, :create, :new, :edit, :update]
  end

  private

  def assign_resource(r)
    instance_variable_set(resource_sym, r)
  end

  def resource_sym
    @resource_sym ||= :"@#{resource.name.underscore.split("/").last}"
  end

  def resource_sym_plural
    @resource_sym_plural ||= :"@#{resource.name.underscore.split("/").last.pluralize}"
  end
end

By doing so, we can write controllers that look something like the following.

class Reservations < Application
  include CrudTemplate

  def resource
    Reservation
  end

  def on_create_success
    flash[:notice] = "Thank you"
    redirect("/")
  end

  protected

  def self.shown_actions
    [:new, :create]
  end

  def resource_attrs
    params[:reservation].merge(session[:member])
  end
end

Things are usually more complicated. The above model falls short for the majority of web applications I've worked on. Resources rarely are direct matches to database tables and there is usually good reason for them not to be. Applications involve complex business logic, spanning further from what a set CRUD operations is appropriate for. One might argue that business logic can be incorporated into Models (as in ORM classes), but I generally prefer to avoid keeping business logic near the persistence layer and opt for a database agnostic, rich domain tier.

This however doesn't imply that controllers shouldn't think in terms of resources. Controllers are close to the web, and the web works well with resources. It suffices for domain layer endpoints that intend to communicate with a controller to expose an interface the controller understands. If we define that interface so that it matches its database specific counterpart, we can achieve the best of both worlds.

web-domain-db

Controllers can transparently operate on plain ruby components which include an AbstractResource module (interface) and choose to implement any of its methods, or directly on ORM models, such as ActiveRecord classes, where appropriate.

module AbstractResource
  attr_reader :params

  def initialize(params = {})
    @params = params
  end

  def save
    raise "Implement me"
  end

  def update_attributes(attrs = {})
    raise "Implement me"
  end

  def valid?
    raise "Implement me"
  end

  def errors
    raise "Implement me"
  end

  module ClassMethods
    def delete(id)
      raise "Implement me"
    end

    def find(id)
      raise "Implement me"
    end
  end

  def self.included(target)
    target.extend(ClassMethods)
  end
end

P.S. Credit due to Carlos Villela whose observations have been the core and inspiration behind the ideas in this article.