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@googlegroups.com.
To unsubscribe from this group, send email to 
rubyonrails-talk+unsubscr...@googlegroups.com.
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.


Reply via email to