Using ActiveResource to consume web-services
Today I’m reviewing Joe Van Dyk’s monkeycharger application, which is a web-service for storing and charging credit cards. I loved looking at this app, because its only interface is a RESTful web service: there is no HTML involved. (If you’ve never written an app that only exposes a web-service UI, you ought to. It’s a blast.)
In general, Joe has done a fantastic job with keeping the controllers slim and moving logic to models. The only significant gripe I had with the application is that it is not ActiveResource compatible.
For those of you that are late to the party, ActiveResource is the newest addition to the Rails family. It lets you declare and consume web-services using an ActiveRecord-like interface…BUT. It is opinionated software, just like the rest of Rails, and makes certain assumptions about the web-services being consumed.
- The service must understand Rails-style REST URLs. (e.g. “POST /credit_cards.xml” to create a credit card, etc.)
- The service must respond with a single XML-serialized object (Rails-style).
- The service must make appropriate use of HTTP status codes (404 if the requested record cannot be found, 422 if any validations fail, etc.).
It’s really not much to ask, and working with ActiveResource (or “ares” as we affectively call it) is a real joy.
However, monkeycharger tends to do things like the following:
1 2 3 4 5 6 7 8 9 10 |
class AuthorizationsController < ApplicationController def create @credit_card = Authorizer.prepare_credit_card_for_authorization(params) transaction_id = Authorizer::authorize!(:amount => params[:amount], :credit_card => @credit_card) response.headers['X-AuthorizationSuccess'] = true render :text => transaction_id rescue AuthorizationError => e render :text => e.message end end |
Three things: the request is not representing an “authorization” object, the response is not XML, and errors are not employing HTTP status codes to indicate failure.
Fortunately, this is all really, really easy to fix. First, you need (for this specific example) an Authorization model (to encapsulate both the the XML serialization and the actual authorization).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Authorization attr_reader :attributes def initialize(attributes) @attributes = attributes end def credit_card @credit_card ||= Authorizer.prepare_credit_card_for_authorization(attributes) end def authorize! @transaction_id = Authorizer.authorize!(:amount => attributes[:amount], :credit_card => credit_card) end def to_xml { :transaction_id => @transaction_id }.to_xml(:root => "authorization") end end |
Then, we rework the AuthorizationsController to use the model:
1 2 3 4 5 6 7 8 |
class AuthorizationsController < ApplicationController def create authorization = Authorization.new(params[:authorization]) authorization.authorize! render :xml => authorization.to_xml, :status => :created rescue AuthorizationError => e render :xml => "<errors><error>#{e.message}</error></errors>", :status => :unprocessable_entity end |
(Note the use of the “created” status, which is HTTP status code 201. Other verbs just use “ok”, status code 200, to indicate success. Also, with an error, we return an “unprocessable_entity” status, which is HTTP status code 422. ActiveResource will treat that as a failed validation.)
With that change, you could now use ActiveResource to authorize a credit card transaction:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Authorization < ActiveResource::Base self.site = "http://my.monkeycharger.site" end auth = Authorization.new(:amount => 15, :credit_card_id => 1234, :remote_key => remote_key_for_card) if auth.save puts "success: #{auth.transaction_id}" else puts "error: #{auth.errors.full_messages.to_sentence}" end |
It should be mentioned, too, that making an app ActiveResource-compatible does nothing to harm compatibility with non-ActiveResource clients. Everything is XML, both ways, with HTTP status codes being used to report whether a request succeeded or not. Win-win!
Obviously, real, working code trumps theoretical whiteboard sketches every time, and Joe is to be congratulated on what’s done. Even though ActiveResource-compatibility can buy you a lot, you should always evaluate whether you really need it and implement accordingly.


So It’s a blast to write webservices nowadays but where is the effort to make it a blast to do blazing frontends nowadays? Answer: Nowhere. Everyone is busy writing services. For example walmart, which were thinking about writing just a backend and establish some app on facebook because nobody would stickaround at walmarts site anyway.
Thanks for this great write-up Jamis.
@Foo, you may want to check out Dave Thomas’ post:
http://pragdave.pragprog.com/pragdave/2007/03/the_radar_archi.html
Inspired by this, I’ve started to implement a CMS with ActiveResource capability built-in. I’m calling it Resource and Content Management, or RACM (pronounced rack ‘em). It’s your standard CMS, but instead of extending it with Plugins (like Wordpress/Mephisto) or Modules (like Drupal), an admin can simply specify a URL for a Resource, give it a name, and use it in her templates. That’s the idea anyway, as I’m developing it right now.
@Luigi, thanks for the link. Dave Thomas really hits the mark sometimes.
@Foo, I think that we’re going to start seeing front-end come to more prominence very soon. There are two reasons for the separation though: Building a really nice service makes external developers more likely to use it (free development), and testing a service in isolation is a hell of a lot easier than when combined with the front end.
My point being that once we have a lot of really well tested, easy to use services floating around, developers that could never quite grok that side but can grok the design side will start showing up.
Already I think we’re seeing this in the Prototype/Scriptaculous arena and the AIR/Gears arena. On the one hand P/S is starting to make html fun again, on the other AIR/Gears is trying to just throw out the whole browser thing and just have happy programs hitting happy services.
Where there’s a frustration there’s soon to be a solution. As long as you’re having the same frustrations as a lot of other people :)
Thanks for the write-up! I’ll work on making those changes—they make sense to me.
One question: how do people test ActiveResource clients / services locally? Do you have something like this in config/environments/test.rb and config/environments/development.rb:
class Authorization < ActiveResource::Base self.site = “http://localhost:4000” end
And then set the site variable to the real one in production.rb?
@Joe, In dev mode, yeah, I’ll set up a local version running on some port and hit it that way. In test mode, I usually just mock it all up, and script fake responses to requests.
Greetings, Interesting write-up. I am just finishing up writing a webservice internally at my work, and I made heavy use of http status codes, and had a real issue with ‘422’. It’s a WebDAV code, not a standard code, and so I didn’t want to use it for error returns. I use 400 instead, in general, and most error returns do NOT return XML.
I’m curious what you think should be returned for errors? Sometimes there is nothing to tell, and the status code says it all (404 especially). Sometimes there are errors (usually validation or illegal combinations). It’s not clear whether to return XML, or let the status code do the talking. So far I’ve opted for the latter, providing errors when it’s reasonable, but no consumer yet relies on the format of error-returns.
So. Do you really think 422 is the right code to use for data errors?
Is XML-returned data expected even when an error status is returned?
I’m not coding for ActiveResource mind you, as (afaik) it’s only edge rails, and this is production code, but I’d love to be able to transition to it in the future.
— Morgan
I’ve active-resourced monkeycharger. Check it: http://monkeycharger.googlecode.com/svn/trunk/app/controllers/authorizations_controller.rb
@Morgan, is the “real issue” you had with the 422 code simply that you didn’t want to use it, since it originated with WebDAV? I do really think it is the right code to use for data errors, since no other HTTP status code specifically covers that case.
And in the case of validation errors, how do you report which attributes failed validation if you don’t return data with the error status?
Can you POST and PUT binary data (e.g. images or swf files) with ActiveResource? Could you give an example? Would much appreciate it.
Thanks for the great writeup.
Do you know when the ActiveResource development is going to settle down and the Rails version number will be bumped up? It still seems to be in a bit of flux…
Hey there,
This is Caroline from SocialRank.
I am trying to get in touch with you but couldn’t find your email address.
We’re launching a new Web 2.0 site dedicated to Ruby On Rails and we have started indexing your blog posts as part of our
content filter.
I’d like to send you an invite to a beta preview. Can you get back to me with your email address.
Mine is caroline@rubygalore.com
Kind regards,
Caroline
www.SocialRank.com