While following patterns suggested by JSON API with Ember may be straightforward, little documentation exists for its usage within Backbone.js. With feed-readers popular in the past few months, I’ve decided to demonstrate how to follow the specification defined by JSON API to provide data to a Backbone application; namely, how to populate collections and models with associated data.
The Application API
Let’s start with the Rails application’s API implementation, which is fairly straightforward:
# app/controllers/api/feeds_controller.rb
class Api::FeedsController < ApplicationController
respond_to :json
def index
feeds = Feed.rss
respond_with feeds, each_serializer: FeedSerializer
end
def show
feeds = Feed.where(id: params[:id]).rss
respond_with feeds, each_serializer: FeedSerializer
end
end
The controller is using ActiveModel::Serializers
to generate JSON responses for both Feed
and Entry
objects.
# app/serializers/feed_serializer.rb
class FeedSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :feed_url, :etag, :description
has_many :entries, serializer: EntrySerializer,
embed: :ids, include: true, key: :entries
end
# app/serializers/entry_serializer.rb
class EntrySerializer < ActiveModel::Serializer
attributes :id, :url, :title, :published, :author, :content
def content
object.content || object.summary
end
end
These classes result in a JSON structure like this when retrieving api/feeds.json
:
{
"entries"=> [
{"id"=>"a9f9283c34af5c6f7ab6fc461e5ba8ee",
"url"=> "",
"title"=> "",
"published"=>"2013-09-22T18:25:02Z",
"author"=>"",
"content"=>""},
{"id"=>"e805ed779257cd0d1f69b747fb01b591",
"url"=> "",
"title"=>"",
"published"=>"2013-09-22T18:16:06Z",
"author"=>"",
"content"=> ""},
{"id"=>"6601a53c98e690a46b8bbeca46a6904a",
"url"=>"",
"title"=>"",
"published"=>"2013-08-30T15:16:03Z",
"author"=>"",
"content"=> ""}
],
"feeds"=> [
{"id"=>"e74786582200ea744c0d0a72fc616199",
"title"=>"",
"url"=>"",
"feed_url"=>"",
"etag"=>"\"4762e3773a5404de50dcfaef49697460\"",
"description"=>"",
"entries"=> ["a9f9283c34af5c6f7ab6fc461e5ba8ee", "e805ed779257cd0d1f69b747fb01b591"]},
{"id"=>"1a33061dccfdc80aff5ce04df9591f6e",
"title"=>"",
"url"=>"",
"feed_url"=>"",
"etag"=>"\"7896d-eaef-4e6c07d3b7300\"",
"description"=> "",
"entries" => ["6601a53c98e690a46b8bbeca46a6904a"]}
]
}
There are two keys, feeds
and entries
, returned. Each item within feeds
maintains a reference to its list of entry IDs, which will need to be
associated when building up the structure within the Backbone collection.
The Backbone Application
Let’s look at the Backbone collection:
# app/assets/javascripts/collections/feeds.coffee
class @App.Collections.Feeds extends Backbone.Collection
model: App.Models.Feed
url: '/api/feeds'
parse: (response) =>
@entries = response.entries
response.feeds
parse
is doing most of the heavy lifting here, assigning a property
entries
to the collection (to be used later) and returning response.feeds
,
which gets assigned internally to models()
.
With the collection of feeds knowing about entries
, it becomes the
responsibility of each App.Models.Feed
to correctly reference each
App.Models.Entry
:
# app/assets/javascripts/models/feed.coffee
class @App.Models.Feed extends Backbone.RelationalModel
relations: [
{
type: Backbone.HasMany
relatedModel: 'App.Models.Entry'
collectionType: 'App.Collections.Entries'
key: 'entries'
}
]
initialize: ->
@set 'entries', _.filter @collection.entries, (entry) =>
_.indexOf(@get('entries'), entry.id) >= 0
title: ->
@get 'title'
description: ->
@get 'description'
entries: ->
@get 'entries'
Within App.Models.Feed
‘s initialize
, we bootstrap the JSON data from the collection’s entries
into the entries
attribute on the App.Models.Feed
model. Because the entries
array may contain IDs of entries for other feeds, however, we filter out everything that doesn’t belong. Once the records have been filtered correctly, Backbone-relational.js handles the rest.
For reference, here are App.Models.Entry
and App.Collections.Entries
:
# app/assets/javascripts/models/entry.coffee
class @FeedMe.Models.Entry extends Backbone.RelationalModel
title: ->
@get 'title'
url: ->
@get 'url'
published: ->
@get 'published'
content: ->
@get 'content'
author: ->
@get 'author'
# app/assets/javascripts/collections/entries.coffee
class @FeedMe.Collections.Entries extends Backbone.Collection
model: FeedMe.Models.Entry
Results
While there is a bit of wiring involved, it’s fairly easy to set up a Backbone app to interact with associated data provided by an API following the patterns of JSON API. If you’re using Marionette.js to handle associated data and have examples to share, tweet @thoughtbot to let us know!
Learn More About Backbone.js
If you’re interested in learning how to build Backbone apps, I recommend you check out thoughtbot’s book Backbone.js on Rails, which includes an example app in addition to the book itself.