Rails 3+ templating system with JSON, XML and Plist support.
Go to file
ccocchi 63b50b2a31 Merge branch 'master' into render-method 2012-07-25 18:37:40 +02:00
lib Merge branch 'master' into render-method 2012-07-25 18:37:40 +02:00
test Don't need to retrieve data variable from context 2012-07-25 18:23:41 +02:00
.gitignore Initial commit 2012-02-22 11:42:38 +01:00
Gemfile Allow to specify json engine used. 2012-07-18 23:42:09 +02:00
Gemfile.lock Allow to specify json engine used. 2012-07-18 23:42:09 +02:00
MIT-LICENSE Update LICENSE 2012-07-24 12:07:26 +02:00
README.md Update LICENSE 2012-07-24 12:07:26 +02:00
Rakefile rabl-fast-json => rabl-rails 2012-04-20 16:28:34 +02:00
rabl-rails.gemspec Allow to specify json engine used. 2012-07-18 23:42:09 +02:00

README.md

RABL for Rails

RABL (Ruby API Builder Language) is a ruby templating system for rendering resources in different format (JSON, XML, BSON, ...). You can find documentation here.

RABL-rails only target Rails 3+ application because Rails 2 applications are becoming less and less present and will be obsolete with Rails 4. So let's look to the future !

So now you ask why used rabl-rails if rabl already exists and supports Rails. Rabl-rails is faster and uses less memory than standard rabl gem while letting you access same features. Of course, there are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.

Installation

Install as a gem :

gem install rabl-rails

or add directly to your Gemfile

gem 'rabl-rails'

And that's it !

Overview

Once you have installed RABL, you can directly used RABL templates to render your resources without changing anything to you controller. As example, assuming you have a Post model filled with blog posts, and a PostController that look like this :

class PostController < ApplicationController
  respond_to :html, :json, :xml

  def index
	  @posts = Post.order('created_at DESC')
	  respond_with(@posts)
  end
end

You can create the following RABL-rails template to express the API output of @posts

# app/views/post/index.rabl
collection :@posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(@user) }

This would output the following JSON when visiting http://localhost:3000/posts.json

[{
  "id" : 5, title: "...", subject: "...",
  "user" : { full_name : "..." },
  "read" : true
}]

That's a basic overview but there is a lot more to see such as partials, inheritance or fragment caching.

How it works

As opposed to standard RABL gem, this gem separate compiling (a.k.a transforming a RABL-rails template into a Ruby hash) and the actual rendering of the object or collection. This allow to only compile the template once and only Ruby hashes.

The fact of compiling the template outside of any rendering context prevent us to use any instances variables (with the exception of node) in the template because they are rendering objects. So instead, you'll have to use symbols of these variables.For example, to render the collection @posts inside your PostController, you need to use :@posts inside of the template.

The only places where you can actually used instance variables are into Proc (or lambda) or into custom node (because they are treated as Proc).

# We reference the @posts varibles that will be used at rendering time
collection :@posts
	
# Here you can use directly the instance variable because it
# will be evaluated when rendering the object 
node(:read) { |post| post.read_by?(@user) }

The same rule applies for view helpers such as current_user

After the template is compiled into a hash, Rabl-rails will use a renderer to do the actual output. Actually, only JSON and XML formats are supported.

Usage

Data declaration

To declare data to use in the template, you can use either object or collection with the symbol name or your data.

# app/views/users/show.json.rabl
object :@user

# app/views/users/index.json.rabl
collection :@users

You can specify root label for the collection using hash or :root option

collection :@posts, root: :articles
#is equivalent to
collection :@posts => :articles

# => { "articles" : [{...}, {...}] }

There are rares cases when the template doesn't map directly to any object. In these cases, you can set data to false or skip data declaration altogether.

object false
node(:some_count) { |_| @user.posts.count }
child(:@user) { attribute :name }

Attributes / Methods

Basic usage is to declared attributes to include in the response. These can be database attributes or any instance method.

attributes :id, :title, :to_s

You can aliases these attributes in your response

attributes title: :foo, to_s: :bar
# => { "foo" : <title value>, "bar" : <to_s value> } 

Child nodes

You can include nested information from data associated with the parent model. You can also alias these associations. For example if you have a Post model that belongs to a User

object :@post
child(user: :author) do
	attributes :name
end
# => { "post" : { "author" : { "name" : "John D." } } }

You can also use arbitrary data source with child nodes

child(:@users) do
	attributes :id, :name
end

Custom nodes

You can create custom node in your response, based on the result of the given block

object :@user
node(:full_name) { |u| u.first_name + " " + u.last_name }
# => { "user" : { "full_name" : "John Doe" } }

You can add the node only if a condition is true

node(:email, if: -> { |u| u.valid_email? }) do |u| 
	u.email
end

Nodes are evaluated at the rendering time, so you can use any instance variables or view helpers inside them

node(:url) { |post| post_url(post) }

Custom nodes are really usefull to create flexible representations of your resources.

Extends & Partials

Often objects have a basic representation that is shared accross different views and enriched according to it. To avoid code redundancy you can extend your template from any other RABL template.

# app/views/users/base.json.rabl
attributes :id, :name

# app/views/users/private.json.rabl
extends 'users/base'
attributes :super_secret_attribute

You can also extends template in child nodes using partial option (this is the same as using extends in the child block)

collection @posts
attribute :title
child(:user, partial: 'users/base')

Partials can also be used inside custom nodes. When using partial this way, you MUST declare the object associated to the partial

node(:location) do |user|
	{ city: user.city, address: partial('users/address', object: m.address) }
end

Nesting

Rabl allow you to define easily your templates, even with hierarchy of 2 or 3 levels. Let's suppose your have a thread model that has many posts and that each post has many comments. We can display a full thread in a few lines

object :@thread
attribute :caption
child :posts do
  attribute :title
	child :comments do
		extends 'comments/base'
	end
end

Performance

Benchmarks have been made using this application, with rabl 0.6.14 and rabl-rails 0.1.0

Overall, Rabl-rails is 20% faster and use 10% less memory.

You can see full tests on test application repository.

Caching

Caching is not a part of Rabl-rails. It is already in Rails itself, because caching all view output is the same as action caching (Rails caching is even better because it will also not run your queries).

And caching each object in a collection can be really not effective with big collections or simple objects. This is also a nightmare with cache expiration.

Authors and contributors

Want to add another format to Rabl-rails ? Checkout JSON renderer for reference Want to make another change ? Just fork and contribute, any help is very much appreciated

Original idea

  • RABL Standart RABL gem. I used it a lot before deciding I wanted faster views

Copyright © 2011-2012 Christopher Cocchi-Perrier. See MIT-LICENSE for details.