gsw
2012-Sep-18 16:28 UTC
"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:
https://github.com/garysweaver/restful_json
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:
https://github.com/nesquena/rabl/issues/88
http://forums.pragprog.com/forums/191/topics/8247
https://github.com/rails/rails/issues/2798
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.
--
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Talk" group.
To post to this group, send email to
rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web visit
https://groups.google.com/d/msg/rubyonrails-talk/-/GWh2EIp2PmgJ.
For more options, visit https://groups.google.com/groups/opt_out.
gsw
2012-Sep-18 16:59 UTC
Re: "best practices" for Rails serving RESTful JSON services for use by AngularJS, Ember.js, 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 -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To view this discussion on the web visit https://groups.google.com/d/msg/rubyonrails-talk/-/PbSyfZmeogkJ. For more options, visit https://groups.google.com/groups/opt_out.
gsw
2012-Sep-18 17:01 UTC
Re: "best practices" for Rails serving RESTful JSON services for use by AngularJS, Ember.js, etc.
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) -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To view this discussion on the web visit https://groups.google.com/d/msg/rubyonrails-talk/-/iAQGlf56yNYJ. For more options, visit https://groups.google.com/groups/opt_out.
gsw
2012-Sep-18 17:29 UTC
Re: "best practices" for Rails serving RESTful JSON services for use by AngularJS, Ember.js, etc.
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. 228–230. ISBN 978-0-596-52926-0. (well- if wikipedia is correct, at least). Massive code fail. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msg/rubyonrails-talk/-/dNAdQHZI9dkJ. For more options, visit https://groups.google.com/groups/opt_out.
gsw
2012-Sep-19 21:35 UTC
Re: "best practices" for Rails serving RESTful JSON services for use by AngularJS, Ember.js, etc.
On Tuesday, September 18, 2012 12:28:00 PM UTC-4, gsw wrote:> > # 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 >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: http://blog.plataformatec.com.br/2009/08/embracing-rest-with-mind-body-and-soul/ 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: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/mime_responds.rb In the process, I started writing this (don''t ask- pretty lame but maybe it will continue): https://github.com/garysweaver/convenient-actionpack -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To view this discussion on the web visit https://groups.google.com/d/msg/rubyonrails-talk/-/lEJqMHz2nd0J. For more options, visit https://groups.google.com/groups/opt_out.