Few days ago a new project was thrown in my face ;). And it was interesting I must say - extracting some heavy internals of existing application into internal API + external client (app), that could be developed further with some additional features.

First I spend few hours do to some research an just to experiment a little, I found this topic on stackoverflow (d’oh) that was quite useful. Obviously first I though of some MV* Javascript framework at first, AngularJS seemed like a great choice, but after messing with it for a while I concluded I won’t be able to learn it in given time-frame I had (sorry Angular, maybe next time!). I decided to go more ruby (backend) way - my weapon of choice was: Active Resource.

ActiveResource was extracted into separate gem in Rails 4, and it basically allows you to route actions on you model to REST API. So you get almost all the benefits as using regular ActiveRecord model like associations, query methods etc. Sounds cool, right? So how to do it in few general steps:

Now that you have basic API setup it’s time to communicate with it. ActiveResource have very poor readme, but great RDoc (that’s a shame, to be honest I prefer just having rock-solid readme file, but maybe that’s just me :P). Few tips you might find useful:

How to modify ActiveResource headers?

Let’s say you want to authenticate by checking HTTP_SECRET header, so let’s create a base class that all of your ActiveResource models will inherit from.

class Base < ActiveResource::Base
  self.headers['SECRET'] = 'wow, so secret, wow' # notice no HTTP_ here

See why this works - source.

How to paginate with ActiveResource?

You can go with not so restful way by using ActiveResource::Collection. See this gist I stolen this blogpost (check this out, it’s very useful <3). Or you can use activeresource-response that have full working example how to implement REST pagination with kaminari. It’s that simple. In case you are using will_paginate on your API side, this should help a little:

  # Kaminari
  response.headers["X-total"] = @orders.total_count.to_s
  response.headers["X-offset"] = @orders.offset_value.to_s
  response.headers["X-limit"] = @orders.limit_value.to_s

  # Will_paginate
  response.headers["X-total"] = @orders.total_entries.to_s
  response.headers["X-offset"] = @orders.offset.to_s
  response.headers["X-limit"] = @orders.per_page.to_s

On the API side you might also use will_paginate, in that case you can just require will_paginate/array (in initializer for example) and paginate array directly by calling .paginate(options) with proper total/page/per_page values - see source.

How to handle remote validation?

You probably don’t want to duplicate validation logic in both API server and the client. So it makes sense to just return validation errors to ActiveResource in a way it can understand it. It should be enough to respond with status 422 (ActiveResource::ResourceInvalid) and pass validation errors:

# Inside API try to save/update_attributes and on failure return this:
render json: { errors: object.errors }, status: 422

Testing & mocks

ActiveResource provides HttpMock class, that allows you to register mock responses. Taken from docs:

# unfortunately can't register regex path at the moment (4.0.0)
ActiveResource::HttpMock.respond_to do |mock|
  mock.post "/people.json", {}, @matz, 201, "Location" => "/people/1.json"
  mock.get "/people/1.json", {}, @matz
  mock.put "/people/1.json", {}, nil, 204
  mock.delete "/people/1.json", {}, nil, 200

You can clear defined request by calling ActiveResource::HttpMock.reset!. If you want to raise an error, you can trigger it by setting proper response code. Once again, taken from docs:

  # * 200..399 - Valid response. No exceptions, other than these redirects:
  # * 301, 302, 303, 307 - ActiveResource::Redirection
  # * 400 - ActiveResource::BadRequest
  # * 401 - ActiveResource::UnauthorizedAccess
  # * 403 - ActiveResource::ForbiddenAccess
  # * 404 - ActiveResource::ResourceNotFound
  # * 405 - ActiveResource::MethodNotAllowed
  # * 409 - ActiveResource::ResourceConflict
  # * 410 - ActiveResource::ResourceGone
  # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
  # * 401..499 - ActiveResource::ClientError
  # * 500..599 - ActiveResource::ServerError
  # * Other - ActiveResource::ConnectionError

Happy coding!