"best practices" for Rails serving RESTful JSON services for use by AngularJS, Ember.js, etc

I’ve been writing a gem to implement and extend common controller
functionality so that Rails can be used with Javascript frameworks like
AngularJS (which we are using), Ember.js, etc. in such a way that the
user
doesn’t have to tweak a a bunch of rails g controller boilerplate code
to
provide services for use in these frameworks that in turn would require
various changes to fit the normal Rails way to specify things.

The gem is here:

It needs a heck of a lot of work still, no working tests at the moment,
and
integrating roar-rails in its 3.0.0 branch.

Our aim/current strategy for web development is to:

  1. Keep # of controllers (and amount of code required for each) to a
    minimum, but they shouldn’t be overly monolithic to the point that they
    become difficult to maintain.
  2. (Only) use REST where it makes sense.
  3. Try to keep logic and operational knowledge out of the client side.

More specifically:

  • Unlike the typical historical Rails app where the controller was
    really
    the controller accessing the model and serving up the view, when you are
    using AngularJS and Ember.js heavily, the primary role of Rails REST-ish
    service provider; not everything fits REST (hypertext/hypermedia doesn’t
    fit every application) and there are definitely going to be custom
    actions.
  • Services need to be able to be defined quickly and need to return
    errors
    in JSON format with relevant status codes, etc.
  • You need to be able to transactionally make alterations to
    associations
    and collections of associations, not just to a single resource.
  • The services also should make it as easy as possible to integrate with
    these frameworks. To persist an associated model you shouldn’t have to
    tack
    on _attributes to the key in the JSON because odds are that the
    Javascript
    app is just storing it as whatever the association name is, or perhaps
    some
    other name that makes more sense. You should be able to send in the JSON
    assocation data for something and the controller should know via mass
    assignment security that you don’t have rights to write the association,
    but you do have rights to change the list of associations, etc. so it
    would
    just change those associations if you told it to allow that, etc. In
    other
    words, accepts_nested_attributes_for is inadequate.

What we’ve tried and looked into as a DRY way to using Rails in large
part
as JSON service provider:

  • RABL: provides an way to do json views (to replace sending options
    into
    as_json/to_json) does not handle incoming JSON to be persisted in a
    similar
    way.
  • ActiveModel::Serializers available now and coming in Rails 4 - similar
    to
    RABL in that it does not map incoming JSON to be persisted.
  • strong_parameters available now and coming in Rails 4 - keeps you from
    being able to accidentally persist something that the controller doesn’t
    specifically define, but does not define JSON view.
  • roar-rails - provides a way to specify both the JSON view and what is
    accepted, so we are attempting to integrate it currently.

Where complication rears its ugly head:

When you see things like this, it looks easy:

def index
  @companies = Company.all
  respond_with @companies
end

But respond_with makes assumptions about what should be called, and then
you should handle errors because it should try to return those as JSON
with
an appropriate HTTP status code (:ok, :unprocessable_entity, :created,
:forbidden, :internal_server_error, etc.), then there is location url
which
I’m not sure if has a place in a service meant for consumption by a
service
meant to be consumed by a javascript app?, etc. For an idea of the
various
things that people have to do and what they run into:

etc.

So you maybe end up with something like this just to handle a POST:

this makes sense for Rails served view, as a failed create, but does

it
makes sense in a JSON service-oriented controller serving to a page
served
by a different controller? We don’t need to retain state in that case,
so
new is never called on its own/doesn’t need to be separated out?
def new
@company = Company.new
respond_with @company
end

I have not tested this- just a possibility of something that would

use
roar-rails which provides consume! and deserialization.
def create
# another method to implement that relies on proper authorization
if can_create?
respond_with(errors: [‘Access denied to create
#{self.class.name}’],
status: forbidden)
end
begin
@company = Company.new(params[:company])
consume! @company
if @company.errors
respond_with(errors: [@company.errors], location: users_url,
status: unprocessable_entity)
else
respond_with(@company, location: users_url, status: created)
end
rescue
puts $!.inspect, $@
# TODO: add support for other formats
respond_to do |format|
format.json { render json: {errors: [$!.message]}, status:
(:internal_server_error) }
end
end
end

If you were writing a more generic REST-ish (not necessarily a
hypertext/hypermedia driven app) controller that could be used to make
everything I described as easy and DRY as possible, such that the
Javascript app writer hardly had to think about Rails at all, and
providing
robust services was mostly just a matter of writing some models and JSON
representations for various views, how would you do it?

I am guessing the standard response is “these things depend on the
environment, so it doesn’t make sense to abstract them into a controller
that will just add another layer of things in the way”, but I’m really
trying to provide something that will help here; we have a ton of legacy
models, etc. that are currently handled by another SOA system that we
need
to replace in piecemeal over time, so what may seem like minor
differences
in the amount of code required will make a big difference for us when it
comes time to us having to upgrade Rails, etc.

On Tuesday, September 18, 2012 12:28:00 PM UTC-4, gsw wrote:

rescue
  puts $!.inspect, $@
  # TODO: add support for other formats
  respond_to do |format|
    format.json { render json: {errors: [$!.message]}, status:

(:internal_server_error) }
end
end

Sorry I overlooked the rescue block (and no idea how I got parens around
the symbol there- must have been copy/paste from something wierd)- may
look
more like:

rescue
  puts $!.inspect, $@
  respond_with(errors: [$!.message], status: :internal_server_error)
end

On Tuesday, September 18, 2012 12:28:00 PM UTC-4, gsw wrote:

    respond_with(errors: [@company.errors], location: users_url,

status: unprocessable_entity)

Ugh. Didn’t need the array around @company.errors either.

respond_with(errors: @company.errors, location: users_url, status:

unprocessable_entity)

On Tuesday, September 18, 2012 12:28:00 PM UTC-4, gsw wrote:

  @company = Company.new(params[:company])
  respond_to do |format|
    format.json { render json: {errors: [$!.message]}, status:

(:internal_server_error) }
end
end
end

Ok, this was pretty wrong. Got off on the wrong foot by looking at this
and
underestimating respond_with:
http://apidock.com/rails/ActionController/MimeResponds/respond_with
It’s better documented here:

And the docs are getting better between rails 3 and 4:

rails 3:
https://github.com/rails/rails/blob/3-2-rel/actionpack/lib/action_controller/metal/mime_responds.rb
master/rails 4:

In the process, I started writing this (don’t ask- pretty lame but maybe
it
will continue):
https://github.com/garysweaver/convenient-actionpack

On Tuesday, September 18, 2012 12:28:00 PM UTC-4, gsw wrote:

    respond_with(errors: [@company.errors], location: users_url,

status: unprocessable_entity)

Sorry, a lot of status symbols missing the preceding colon. Also, I
think
the location only needs to be set for HTTP status codes 201 (:created)
and
202 (:accepted) according to Leonard, Richardson (2007)- RESTful Web
Services. Sebastopol: O’Reilly. pp. 228230. ISBN 978-0-596-52926-0.
(well-
if wikipedia is correct, at least). Massive code fail.