This guide continues on from the Rails guide, and probably isn’t applicable to day to day development, but will help you with some of the more complicated aspects down the line.
An alternative to using Sprockets for Spine integration is Stitch. Stitch has the advantage that it outputs CommonJS modules, rather than use just simple concatenation. Using Stitch is beyond the scope of this guide, please see its README for more information.
When sending JSON Ajax requests, Spine doesn’t prefix any of it’s data with the model name. For example, once a record has been created, Spine will will send a POST request with a payload looking like this:
{"name": "Sam Seaborn"}
Instead of something most Rails programmers are familiar with, prefixed data:
{"user": {name: "Sam Seaborn"}}
The reason for this design decision is that the prefix is often redundant data; the application already knows the format and indented destination of the data by the context of the HTTP request.
Fortunately Rails 3.1 has added default support for un-prefixed parameters, via a feature called wrap parameters. You should find a file under config/initializers/wrap_parameters.rb
that looks like this:
ActionController::Base.wrap_parameters format: [:json]
# Disable root element in JSON by default.
if defined?(ActiveRecord)
ActiveRecord::Base.include_root_in_json = false
end
Spine also requires responses from the server to be un-prefixed. As in the example above, ActiveRecord::Base.include_root_in_json
should be false
.
Both these settings are the default in Rails 3.1, and you shouldn’t need to alter them.
Spine’s Ajax module %>) is fully REST compatible, and will work with Rails out of the box. One thing to look out for, is that Spine expects create
and update
actions to return the record object.
respond_to :html, :json
def create
@page = Page.new(params[:page])
if @page.save
respond_with(@page, status: :created, location: @page)
else
respond_with(@page.errors, status: :unprocessable_entity)
end
end
def update
@page = Page.find(params[:id])
if @page.update_attributes(params[:page])
respond_with(@page)
else
respond_with(@page.errors, status: :unprocessable_entity)
end
end
For a demonstration of Spine communicating with a Rails controller, see the example application.
To find out more information about Ajax & Spine, please see its guide %>).
Although Spine generates a ID for records client-side, most Rails apps will use server-side generated IDs. The latter approach has a few advantages, as server-side generated IDs are guaranteed to be unique, and usually automatically generated by the database. If you want to use a server-side generated ID, simply return it as part of the record response to the create
action:
# POST /pages returns:
{"id": 1, "name": "POTUS"}
Spine will use the server generated ID from then on, and does some clever stuff to merge its internal ID so old ID references (such as in routes) still work.
A limitation of Ajax is the same-origin policy which restricts Ajax requests to the same domain and port as the page was loaded from. This is for security reasons, and prevents CSRF attacks.
However, while the same origin policy is integral to the security of the Web, it’s also somewhat inconvenient for developers trying to access legitimate remote resources. Cross Origin Requests, or CORS, lets you break out of the same origin policy, giving you access to authorized remote servers.
Using CORS is trivially easy, and is just the matter of adding a few authorization headers to request responses. The specification is well supported by the major browsers, although IE ignored the spec and created it’s own object, XDomainRequest, which has a number of arbitrary restrictions and limitations. Luckily, it’s easily shimed.
CORS support by browser:
You can specify an external host for Spine to use by setting the Spine.Model.host
option, like so:
Spine.Model.host = "http://api.myservice.com"
Once set, all of Spine’s subsequent Ajax requests will be made on that endpoint.
Let’s create a cor
method, which will add some of the request access control headers to the request’s response.
Add the following to app/application_controller.rb
:
before_filter :cor
def cor
headers["Access-Control-Allow-Origin"] = "js-app-origin.com"
headers["Access-Control-Allow-Methods"] = %w{GET POST PUT DELETE}.join(",")
headers["Access-Control-Allow-Headers"] = %w{Origin Accept Content-Type X-Requested-With X-CSRF-Token}.join(",")
head(:ok) if request.request_method == "OPTIONS"
end
Although Access-Control-Allow-Origin
takes a wildcard, I highly recommend not using it as it opens up your app to all sorts of CSRF attacks. Using a whitelist is much better and more secure.
The Access-Control-Allow-Headers
section is important, especially the X-Requested-With
header. Rails doesn’t like it if you send Ajax requests to it without this header, and ignores the request’s Accept
header, returning HTML when it should in fact return JSON.
It’s worth noting that jQuery doesn’t add this header to cross domain requests by default. This is an issue that Spine solves internally, but if you’re using plain jQuery for CORs, you’ll need to specify the header manually.
jQuery.ajaxSetup({
headers: {"X-Requested-With": "XMLHttpRequest"}
});
Some browsers send an options request to the server first, to make sure the correct access headers are set. You’ll need to catch this in Rails, returning a 200
status with the correct headers. To do this, add the following to your application’s config/routes.rb
file:
match '*all' => 'application#cor', :constraints => {:method => 'OPTIONS'}
That’s it, you’re all set up for Cross Origin Requests with Spine!