In the days of Rails 1.x, building a SOAP service in Rails was easy. There was the actionwebservice gem that enabled SOAP services, similar to what we know as ActionResource today.
Be it good or bad, SOAP eventually got replaced by REST within the Rails world, leaving the net scattered with examples on how to build a SOAP service with Rails 1.x but not much for 2.x
The first thing I tried (but did not work):
Inspired by http://www.devarticles.com/c/a/Ruby-on-Rails/Web-Forms-and-Ruby-on-Rails/2/
$ gem install actionwebservice
This installed ok, bringing all the older versions of action series gems along with it.
$ ./script/generate web_service ...
It did not have the generator, so much for this solution
The solution: enter datanoise actionwebservice:
The guys from datanoise, brought it back alive with a version that works in rails 2.x again.
Details on how to use it, can be found on http://www.datanoise.com/articles/2008/7/2/actionwebservice-is-back
I'll redo the example done in the rails 1.x article to the datanoise style
$ gem install datanoise-actionwebservice --source http://gems.github.com ./script/generate .... Installed Generators Rubygems: web_service Builtin: controller, helper, integration_test, mailer, metal, migration, model, observer, performance_test, plugin, resource, scaffold, session_migration ....As you can see, it puts its files in /app/services instead of app/api .
$ ./script/generate web_service Item create app/services/ exists app/controllers/ exists test/functional/ create app/services/item_api.rb create app/controllers/item_controller.rb create test/functional/item_api_test.rb
Next step is to add the gem to the conf/environment.rb so that is knows the correct actionwebservice gem
config.gem 'datanoise-actionwebservice', :lib => 'actionwebservice'Modify the file app/controllers/item_controller.rb
class ItemController < ApplicationController wsdl_service_name 'Item' web_service_api ItemApi web_service_scaffold :invocation if Rails.env == 'development'Modify the file app/controllers/item_api.rb
def add(name, value) Item.create(:name => name, :value => value).id end
def edit(id, name, value) Item.find(id).update_attributes(:name => name, :value => value) end
def fetch(id) @item1=Item.find(id) Item.new(:id => "{@item1.id}", :value => "#{@item1.value}", :name => "#{@item1.name}") end end
class ItemApi < ActionWebService::API::Base api_method :add, :expects => [:string, :string], :returns => [:int] api_method :edit, :expects => [:int, :string, :string], :returns => [:bool] api_method :fetch, :expects => [:int], :returns => [Item] endOk, let's start the service know, and see if we can read the wsdl file
$ ./script/server $ curl http://localhost:3000/item/wsdl <?xml version="1.0" encoding="UTF-8"?> <definitions name="Item" xmlns:typens="urn:ActionWebService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="urn:ActionWebService" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <xsd:schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:ActionWebService"> <xsd:complexType name="Item"> <xsd:all> <xsd:element name="id" type="xsd:int"/> <xsd:element name="name" type="xsd:string"/> <xsd:element name="value" type="xsd:string"/> <xsd:element name="created_at" type="xsd:dateTime"/> <xsd:element name="updated_at" type="xsd:dateTime"/> </xsd:all> </xsd:complexType> </xsd:schema> </types> .......So far so good, let's build a Soap Client to use the service via ruby. Create a file test.rb
$ curl http://localhost:3000/item/invocation ...some..nice..readable..html...
require 'soap/wsdlDriver' wsdl = "http://localhost:3000/item/service.wsdl" item_server = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver item_id = item_server.add('foo', 'bar') if item_server.edit(item_id, 'John', 'Doe') puts 'Hey, it worked!' else puts 'Back to the drawing board...' end # Hey, it worked! item = item_server.fetch(item_id) item.class # => SOAP::Mapping::Object item.name # => "John" item.value # => "Doe"If we run this, it will spit out the error “Cannot Map {objType} to SOAP/OM”.
It seems to be a problem with the way the Arrays are handled. I'm unsure how to describe the real issue, but here are some links about the subject.
- http://benrobb.com/2007/02/06/cannot-map-objtype-to-soapom/
- http://www.evenflow.nl/2009/04/02/rails-and-soap/comment-page-1/
- http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/211011
def fetch(id) @item1=Item.find(id) Item.new(:id => "{@item1.id}", :value => "#{@item1.value}", :name => "#{@item1.name}") endThe example now works again. So far, Soap for Rails seems to be working ok.
P.S. If you are consuming it with axis2, chances are that it uses 'Transfer-Encoding: chuncked'. Actionwebservice doesn't seem to handle this ok.