XML-RPC under Ruby on Rails

On a current project, I needed to develop a series of web services for a custom single-signon (unified login) for a bunch of different websites to share. The project needed to be in Ruby on Rails, since that is what is available to the servers, and needed to use a protocol which PHP, Java and Ruby could all understand.

At first I tried to investigate using ActiveResource, but I found this to be excessively Rails-centric, and it only seemed to provide basic CRUD functionality. I needed these webservices to do a lot more work, with a lot more parameters. Since the Rails community (and a lot of web developers in general) seem to rave about RESTful services, my next direction was to write a custom series of REST-based webservices. As I proceeded, it became glaringly obvious that REST services have on major shortcoming – complex data.

Since the basic idea of REST is that all parameters become part of the URI itself (something, I might add, ActiveResource violates right away), you immediately have a problem when it comes to things like street addresses. Everyone’s solution is to use basic HTTP parameters or to URL-encode the data, but to me these solution tainted the point of REST, and made for some truly hideous looking URL’s, respectively.

I also didn’t like the lack of type control. This same lack also negated using JSON-RPC or even some custom YAML-based solution. All the experiences I had with SOAP left a bad taste in my mouth for that one, so what was I to do?

Thankfully, I discovered that Ruby had XML-RPC functionality built right in. But the problem arose that all examples I could find of using it (since Ruby’s documentation is complete and utter dog-sh*t) only showed the XML-RPC server running in stand-alone mode. I certainly couldn’t do this. So after much tinkering, I herewith present a controller class which you can extend to offer very simple XML-RPC calls from within a Rails environment:

#
# This class provides a framework for XML-RPC services on Rails
#
require 'xmlrpc/server'
class WebServiceController < ApplicationController

  # XML-RPC calls are not session-aware, so always turn this off
  session :off

  def initialize
    @server = XMLRPC::BasicServer.new
    # loop through all the methods, adding them as handlers
    self.class.instance_methods(false).each do |method|
      unless ['index'].member?(method)
        @server.add_handler(method) do |*args|
          self.send(method.to_sym, *args)
        end
      end
    end
  end

  def index
    result = @server.process(request.body)
    puts "\n\n----- BEGIN RESULT -----\n#{result}\n----- END RESULT -----\n"
    render :text => result, :content_type => 'text/xml'
  end

end

Here is a working example of using the above code:

class StringController < WebServiceController

  def upper_case(s)
    s.upcase
  end

  def down_case(s)
    s.downcase
  end

end

Invoking the remove method couldn’t be any simpler:

require 'xmlrpc/client'
require 'pp'


server = XMLRPC::Client.new2("http://localhost:3010/string")

result = server.call("upper_case", "This is my string")
pp result

result = server.call("down_case", "This is my string")
pp result

I certainly hope this simple bit of Ruby will help anyone else who may have suffered trying to figure this out as I have.

Enjoy!

When “Agile”, “Dynamic” and “Typeless” Become a Hindrance.

In recent years, I’ve seen the apparent rising popularity of “Agile” programming, powered by “dynamic” languages such as “Ruby”. While these things seem warm and fluffy at first, in the long term with large projects, they really can become a difficult beast to control.

Let’s take “Ruby on Rails” as an example. I was recently tasked with upgrading our version of Rails from 1.2.3 all the way up to 2.3.2, no easy task to be sure. We decided that a gradual, version-by-version upgrade would work the best, since it would allow us to catch deprecations and handle the framework changes a bite at a time.

What I discovered in the end was that Rails itself uses so much “magic”, and directly modifies so many core Ruby API’s that it was impossible to predict what would break between versions. As an example, a minor version change from 1.2.3 to 1.2.4 caused unexpected changes in how dates were handled. The change-logs made no reference to such modifications. The change from 2.1 to 2.2 resulted in a host of modules no longer being identified. Oh sure, if you printed it out, Ruby “thought” it was available, but the minute you tried to actually use anything within the module, you would receive “uninitialized constant” errors on the module name itself.

I know everyone has their own opinions on the matter, but from where I stand using Ruby on Rails on a system even vaguely large and complex simply raises far too many question marks on predictability and a clean upgrade path.

As a comparison, let’s refer to Java 2 Enterprise. Every piece of code I have ever written in J2EE version 1.0 runs without any issue under a J2EE version 3 container. Not even a re-compile was required. I do realise that the comparison is somewhat unfair – but the point still remains. I have very rarely needed to make major changes to any Java-based application to cater for a new framework – especially between minor version numbers.

I hate to make a blanket case for all agile/dynamic frameworks, so I’ll admit that was probably unfair (Grails, Django are on my to-do list). However, my case is even held fast on a more superficial level – my own benchmarks on a variety of languages clearly show that although time and effort may be saved on initial development time, dynamic/typeless languages will always suffer in raw speed, and agile frameworks will always suffer from a degree of unpredictability when upgrading (this becomes immensely exaggerated when “auto-magic” comes into play, as there is no clear path when anything does break).

To be clear, I won’t abandon agile frameworks – if I just want a simple website up, and expect maybe a dozen hits a month, I’m certainly not going to do it in a full blown enterprise container – but in the same token, I would certainly not use any such framework in a banking environment!