Rails and SOAP

For a project, I'm currently implementing a SOAP API in Rails. The Rails application will function as a SOAP server and I'm using the datanoise activewebservice plugin for this. Implementing a SOAP API is pretty easy with this, however, I ran into some problems with returning complete model objects.

I have a model called Customer that I want to expose via my API. After setting up the API controller, I specified the following in /app/services/customer_api.rb:

class CustomerAPI < ActionWebService::API::Base
  api_method :get, :expects => [:int], :returns => [Customer]
end

Implemented with:

def get(id)
  return Customer.find(id)
end

Requesting the WSDL nicely gives the definition of Customer, but when making a request for a specific customer errored with the message "Cannot map Customer to SOAP/OM.". It appears that boolean values (in Ruby presented as true/false) and emtpy values (presented in Ruby as nil), were the problem. SOAP wants them as "true", "false" and "".

I solved this by extending my models with a to_struct() method that returns all attributes in a Struct. I extended both ActiveRecord::Base instances to get the instance method to_struct() inside my models and I extended ActiveRecord::Base class so it would return a Struct. The latter is needed, since the SOAP plugin wants to know the attributes and types of the Struct, without actually creating one. Here's what I did:

To extend ActiveRecord::Base with class methods create a module and put the following in your /config/environment.rb:

require 'model_extensions'
ActiveRecord::Base.send(:extend, ModelExtensions)

Then in the module:

module ModelExtensions
  def struct
    # Check if the DataStruct is already defined
    begin
      eval("#{self}::DataStruct")
    rescue
      # Define the DataStruct class and fill it with
      # members that resemble the model
      class_eval <<-EOF
        class DataStruct < ActionWebService::Struct
          #{self}.columns.map{|c| member c.name.to_s, c.type.to_s}
        end
      EOF
    end
    return eval("#{self}::DataStruct")
  end
end

Now to create a DataStruct in a model, we define the following:

module ActiveRecord
  class Base
    def to_struct
      # Get a DataStruct and instantiate
      struct = self.class.struct.new
      # Set the attributes after clean-up
      self.attributes.each do |k,v|
        v = (v.nil?) ? "" : ((v == true) ? "true" : (v == false) ? "false" : v)
        struct.send("#{k}=", v)
      end
      return struct
    end
  end
end

To finish off, we change the API implementation a bit to the following:

class CustomerAPI < ActionWebService::API::Base
  api_method :get, :expects => [:int], :returns => [Customer.struct]
end

Implemented with:

def get(id)
  return Customer.find(id).to_struct
end

4 Responses to “Rails and SOAP”


  1. 1 orngreen

    Looks exactly like something I could use – But …

    I’ve added your code to /config/environment.rb
    (took some tries before I found out it had to be in the very bottom of that :)

    model_extensions.rb placed in /lib … and works.

    But I can’t find out where to place the code that defines the to_struct ???

    Can you please help? Until now I have resorted to making a ActionWebService::Struct for each of my models, so this can really save me some coding! :)

    Thanks in advance!

  2. 2 wouter

    What I did was putting my ActiveRecord::Base extensions (the to_struct and I have some others) in the model_extensions.rb in /lib. Just below the ModelExtensions module. Looks like this:

    module ModelExtensions

    end
    module ActiveRecord
    class Base

    end
    end

    As long as these things are loaded somewhere.

  3. 3 orngreen

    Wouter: Thank you! It works now !!!

    Should have thought of that actually – just got a little confused since the code was split in two parts, so I assumed they had to be in two different places :)

    Now I know what to spend my easter-coding with :)

  1. 1 Building SOAP service in Rails - High Tech Sorcery

Leave a Reply