class IngredientsController < ApplicationController respond_to :json def index respond_with Ingredients.all end endand requesting it:
curl http://localhost:3000/ingredients.json => [ {"id":1,"created_at":"2014-08-09T19:08:44Z","name":"Basil","illegal":false}, {"id":2,"created_at":"2014-08-09T19:08:56Z","name":"Ginger","illegal":false}, {"id":3,"created_at":"2014-11-09T19:13:38Z","name":"Sassafras","illegal":true} ]which JSONifies the found Ingredient objects and responds them in a very concise manner. It works fine for simple use cases.
But oftentimes use cases get more complex and then a primitive JSON mapping does not satisfy the requirements adequate. For example, when:
- the collection response is tremendous and responding only the relevant object attributes makes a huge payload difference
- the client receiver Javascript API expects a dedicated JSON object signature (e.g autocompleters often expect 'id' and 'name')
- the responding JSON objects require access to custom ActiveRecord methods
Then the Rabl gem comes into play. It is a templating module for JSON objects, which provides the required flexibility.
It is easy included into the Ruby on Rails project (Gemfile):
gem 'rabl'For example a new requirement wants an autocompleter to list the searched ingredients by their name with acronym in brackets. A solution could be refactoring the controller:
class IngredientsController < ApplicationController respond_to :json def index @ingredients = Ingredients.search params[:search] respond_with @ingredients end endand extending the model:
class Ingredient < ActiveRecord::Base def self.search term where("#{table_name}.name LIKE :term OR #{table_name}.description LIKE :term", { term: "%#{term}%" }) end endFinally the view (ingredients/index.json.rabl):
collection @ingredients, object_root: false attributes :id node(:name) { |ingredient| text = ingredient.name text << " (illegal)" if ingredient.illegal? text }needs some explanation. At first the instance variable @ingredients contains the ingredients collection. The default root node can be removed by setting the option object_root: false.
The required attributes are defined by assigning them to attributes and custom nodes can be defined by passing a block to node.
There are more options and the Rabl API is way more flexible when it comes to child nodes, gluing attributes, partials, inheritance, deep nesting, caching etc.
Requesting the ingredients:
curl http://localhost:3000/ingredients.json?search=as => [ {"id":1,"name":"Basil"}, {"id":3,"name":"Sassafras (illegal)"} ]Responding to a request for a specific object could look like:
class IngredientsController < ApplicationController respond_to :json def show @ingredient = Ingredients.find params[:id] respond_with @ingredient end endand its view (ingredients/show.json.rabl):
object @ingredient attributes id: :id, to_s: :title child :foods, object_root: 'meal' do attribute :name endRequesting it:
curl http://localhost:3000/ingredients/1.json => {"ingredient":{"title":"Basil","foods":[ {"meal":{"name":"Garlic Basil Shrimp"}}, {"meal":{"name":"Basil and Lime Sorbet"}} ] }}
Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17
Keine Kommentare:
Kommentar veröffentlichen