class IngredientsController < ApplicationController
respond_to :json
def index
respond_with Ingredients.all
end
end
and 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
end
and 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
end
Finally 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
end
and 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