Fork me on GitHub

Sinatra-backbone

Neat integration of Backbone.js with Sinatra

Do you like Backbone.js? Do you like Sinatra? Did you ever wish you can use them together? Well now you can, with the new Sinatra Backbone!

Usage

This is a Ruby gem.
$ gem install sinatra-backbone --pre

Or do you use Bundler?
gem 'sinatra-backbone', :require => 'sinatra/backbone'

Contents

Sinatra-backbone is comprised of two Sinatra plugins:

JstPages module

A Sinatra plugin that adds support for JST (JavaScript Server Templates). See JstPages example for a full example application.

Basic usage

require 'sinatra/jstpages'

class App < Sinatra::Base
  register Sinatra::JstPages
  serve_jst '/jst.js'
end

Register the Sinatra::JstPages plugin in your application, and use serve_jst. This example serves all JST files found in /views/**/*.jst.* (where /views is your views directory as defined in Sinatra's settings.views) as http://localhost:4567/jst.js.


<script type='text/javascript' src='/jst.js'></script>

You will need to link to the JST route in your layout. Make a <script> tag where the src='...' attribute is the same path you provide to serve_jst.


# views/editor/edit.jst.tpl
<h1>Edit <%= name %></h1>
<form>
  <button>Save</button>
</form>

So, if you have a JST view placed in views/editor/edit.jst.tpl:


// Renders the editor/edit template
JST['editor/edit']();

// Renders the editor/edit template with template parameters
JST['editor/edit']({name: 'Item Name'});

Now in your browser you may invoke JST['templatename']:


Using Sinatra-AssetPack?

TIP: If you're using the sinatra-assetpack gem, just add /jst.js to a package. (If you're not using Sinatra AssetPack yet, you probably should.)

Supported templates

# views/editor/edit.jst.jade
h1= "Edit "+name
  form
    button Save

Jade (.jst.jade) -- Jade templates. This requires jade.js. For older browsers, you will also need json2.js, and an implementation of String.prototype.trim.


# views/editor/edit.jst.tpl
<h1>Edit <%= name %></h1>
<form>
  <button>Save</button>
</form>

Underscore templates (.jst.tpl) -- Simple templates by underscore. This requires underscore.js, which Backbone also requires.


# views/editor/edit.jst.haml
%h1= "Edit "+name
  %form
    %button Save

Haml.js (.jst.haml) -- A JavaScript implementation of Haml. Requires haml.js.


# views/editor/edit.jst.eco
<h1>Edit <%= name %></h1>
<form>
  <button>Save</button>
</form>

Eco (.jst.eco) -- Embedded CoffeeScript templates. Requires eco.js and coffee-script.js.


You can add support for more templates by subclassing the Engine class.

serve_jst(path, [options]) class method

Serves JST files in given path.

JstPages::Engine class

A template engine.

Adding support for new template engines

module Sinatra::JstPages
  class MyEngine < Engine
    def function() "My.compile(#{contents.inspect})"; end
  end

  register 'my', MyEngine
end

You will need to subclass Engine, override at least the function method, then use JstPages.register.

This example will register .jst.my files to a new engine that uses My.compile.


file attribute

The string path of the template file.

contents method

Returns the contents of the template file as a string.

function method

The JavaScript function to invoke on the precompile'd object.

What this returns should, in JavaScript, return a function that can be called with an object hash of the params to be passed onto the template.

RestAPI module

require 'sinatra/restapi'

class App < Sinatra::Base
  register Sinatra::RestAPI
end

A plugin for providing rest API to models. Great for Backbone.js.

To use this, simply register it to your Sinatra Application. You can then use rest_create and rest_resource to create your routes.


RestAPI example

Here's a simple example of how to use Backbone models with RestAPI. Also see the example application included in the gem.

Model setup

db = Sequel.connect(...)

db.create_table :books do
  primary_key :id
  String :title
  String :author
end

class Book < Sequel::Model
  # ...
  def to_hash
    { :title => title, :author => author, :id => id }
  end
end

Let's say you have a Book model in your application. Let's use Sequel for this example, but feel free to use any other ORM that is ActiveModel-compatible.

You will need to define to_hash in your model.


Sinatra

require 'sinatra/restapi'

class App < Sinatra::Base
  register Sinatra::RestAPI

  rest_create '/book' do
    Book.new
  end

  rest_resource '/book/:id' do |id|
    Book.find(:id => id)
  end
end

To provide some routes for Backbone models, use rest_resource and rest_create:


JavaScript

Book = Backbone.Model.extend({
  urlRoot: '/book'
});

In your JavaScript files, let's make a corresponding model.


book = new Book;
book.set({ title: "Darkly Dreaming Dexter", author: "Jeff Lindsay" });
book.save();

// In Ruby, equivalent to:
// book = Book.new
// book.title  = "Darkly Dreaming Dexter"
// book.author = "Jeff Lindsay"
// book.save

Now you may create a new book through your JavaScript:


book = new Book({ id: 1 });
book.fetch();

// In Ruby, equivalent to:
// Book.find(:id => 1)

Or you may retrieve new items. Note that in this example, since we defined urlRoot() but not url(), the model URL with default to /[urlRoot]/[id].


book.destroy();

Deletes will work just like how you would expect it:


rest_create(path, &block) method

class App < Sinatra::Base
  rest_create "/documents" do
    Document.new
  end
end

Creates a create route on the given path.

This creates a POST route in /documents that accepts JSON data. This route will return the created object as JSON.

When getting a request, it does the following:

  • A new object is created by yielding the block you give. (Let's call it object.)

  • For each of the attributes, it uses the attrib_name= method in your record. For instance, for an attrib like title, it wil lbe calling object.title = "hello".

  • if object.valid? returns false, it returns an error 400.

  • object.save will then be called.

  • object's contents will then be returned to the client as JSON.

See the example.


rest_resource(path, &block) method

class App < Sinatra::Base
  rest_resource "/document/:id" do |id|
    Document.find(:id => id)
  end
end

Creates a get, edit and delete route on the given path.

The block given will be yielded to do a record lookup. If the block returns nil, RestAPI will return a 404.

In the example, it creates routes for /document/:id to accept HTTP GET (for object retrieval), PUT (for editing), and DELETE (for destroying).

Your model needs to implement the following methods:

  • save (called on edit)
  • destroy (called on delete)
  • <attrib_name_here>= (called for each of the attributes on edit)

If you only want to create routes for only one or two of the actions, you may individually use:

  • rest_get
  • rest_edit
  • rest_delete

All the methods above take the same arguments as rest_resource.


rest_get(path, &block) method

This is the same as rest_resource, but only handles GET requests.

rest_edit(path, &block) method

This is the same as rest_resource, but only handles PUT/POST (edit) requests.

rest_delete(path, &block) method

This is the same as rest_resource, but only handles DELETE (edit) requests. This uses Model#destroy on your model.

JSON conversion

class Album < Sequel::Model
  def to_hash
    { :id     => id,
      :title  => title,
      :artist => artist,
      :year   => year }
  end
end

The create and get routes all need to return objects as JSON. RestAPI attempts to convert your model instances to JSON by first trying object.to_json on it, then trying object.to_hash.to_json.

You will need to implement #to_hash or #to_json in your models.


Helper methods

There are some helper methods that are used internally be RestAPI, but you can use them too if you need them.

rest_respond(object)

Responds with a request with the given object.

This will convert that object to either JSON or XML as needed, depending on the client's preferred type (dictated by the HTTP Accepts header).

rest_params

Returns the object from the request.

If the client sent application/json (or text/json) as the content type, it tries to parse the request body as JSON.

If the client sent a standard URL-encoded POST with a model key (happens when Backbone uses Backbone.emulateJSON = true), it tries to parse its value as JSON.

Otherwise, the params will be returned as is.

Acknowledgements

© 2011, Rico Sta. Cruz. Released under the MIT License.

Sinatra-Backbone is authored and maintained by Rico Sta. Cruz with help from it's contributors. It is sponsored by my startup, Sinefunc, Inc.