Playing with SOAP in Ruby on Rails using WSDL Driver Factory
-First, a little background on why I am using RoR and SOAP.
When I helped design and build an inventory and booking system circa 2007, my role was basically systems/business analyst. I also provided the impetus that we build the system using tools beyond simple "procedural" PHP/javascript, and that we look for a web application technology platform which would allow us to rapidly build a system that stored everything in a relational database.
Back in 2001, I wondered why I had to write SQL queries for web applications when I knew that if I was working with a well-designed database, there should be techniques to generate queries or even better... a developer could write objects that were basically entities from an ERD and actors from a UML and these objects knew which other objects were related to it and had methods to access all the information without having to write any SQL or be intimately aware of the relationships between tables.
So for our inventory and booking system, our developer found the Ruby on Rails (RoR) framework, which implements MVC architecture. This framework fit my requirements that we don't have to be writing lots of SQL and that we should use an agile method of software development. Eventually, our inventory and booking system was built with RoR and AJAX technologies but my role in its creation did not involve any coding.
Today, I need to build a system to integrate two applications, one that exposes a SOAP API and one that provides decent docoumentation to directly access the database.
-End Background
As a complete newbie to writing code for SOAP and for Ruby on Rails, I have encountered many challenges and tackled problems that an experienced RoR developer would have solved readily. It doesn't help that programming is merely a hobby of mine by necessity, and that I haven't written any serious code in years.
By the end of this post some of the most valuable websites available for someone trying to use RoR to consume SOAP services will be linked and referenced.
Starting with the WSDL Driver Factory standard library by using Ryan Heath's Consuming SOAP services in Ruby example. And similar examples.
The goal was to use his code in a model-view-controller architecture and being a novice, I had a lot of difficulty. I ended up with the following proof of concept; with minimal coding, RoR can consume a simple SOAP webservice. The specific webservice consumed in this example is one that calculates the amortized payment amount per period given the required parameters. I found this webservice on www.xmethods.net, and the developer appears to have implemented it in ColdFusion. Notice in this example, the class Amortization extends ActiveRecord, so the database is used to store persistent data, which may be useful if retaining a history of request inputs and response outputs from consuming a SOAP service is important.
This is the model file: amortization.rb
This is the controller file: amortizations_controller.rb
Below are the views in the views/amortizations folder.
Beginning with new.html.erb
show.html.erb
index.html.erb
You will need to add the following to the routes file found in config/routes.rb.
Also, create a migration for creating amortizations table in the database using the file below and run
In the next code sample, the data is not persistent and Amortization is not a subclass of Active Record. This could be useful, for example, when calling authentication methods of a webservice where you would not want to store the username/password. Thanks to the people in the Ruby on Rails IRC channel, the first method only needed minor modifications. Check out the channel #rubyonrails on irc.freenode.net.
The model, amort.rb
The controller, amorts_controller.rb
new.html.erb
show.html.erb
Don't forget to add a RESTful route for amorts into the routes.rb file. There is extra code in this sample, ie. the create method for the controller is not necessary and could be bypassed without a loss of functionality simply by changing the
Other resources:
Another example
Ruby Standard Library Documentation (RDoc)
The full source of SOAP4R with examples is available at the "trac"-ing site
IRC:
If you want to use a controller method in a view, in the controller you need to specify helper_method :use_web_service, then you can call use_web_service in your views directly. That won't pass in a params hash, though, so you're better off creating a method that takes an array of arguments, and calling that from your use_web_service action and your views.
[code]Try at refactoring... http://pastie.org/398113[/code]
When I helped design and build an inventory and booking system circa 2007, my role was basically systems/business analyst. I also provided the impetus that we build the system using tools beyond simple "procedural" PHP/javascript, and that we look for a web application technology platform which would allow us to rapidly build a system that stored everything in a relational database.
Back in 2001, I wondered why I had to write SQL queries for web applications when I knew that if I was working with a well-designed database, there should be techniques to generate queries or even better... a developer could write objects that were basically entities from an ERD and actors from a UML and these objects knew which other objects were related to it and had methods to access all the information without having to write any SQL or be intimately aware of the relationships between tables.
So for our inventory and booking system, our developer found the Ruby on Rails (RoR) framework, which implements MVC architecture. This framework fit my requirements that we don't have to be writing lots of SQL and that we should use an agile method of software development. Eventually, our inventory and booking system was built with RoR and AJAX technologies but my role in its creation did not involve any coding.
Today, I need to build a system to integrate two applications, one that exposes a SOAP API and one that provides decent docoumentation to directly access the database.
-End Background
As a complete newbie to writing code for SOAP and for Ruby on Rails, I have encountered many challenges and tackled problems that an experienced RoR developer would have solved readily. It doesn't help that programming is merely a hobby of mine by necessity, and that I haven't written any serious code in years.
By the end of this post some of the most valuable websites available for someone trying to use RoR to consume SOAP services will be linked and referenced.
Starting with the WSDL Driver Factory standard library by using Ryan Heath's Consuming SOAP services in Ruby example. And similar examples.
The goal was to use his code in a model-view-controller architecture and being a novice, I had a lot of difficulty. I ended up with the following proof of concept; with minimal coding, RoR can consume a simple SOAP webservice. The specific webservice consumed in this example is one that calculates the amortized payment amount per period given the required parameters. I found this webservice on www.xmethods.net, and the developer appears to have implemented it in ColdFusion. Notice in this example, the class Amortization extends ActiveRecord, so the database is used to store persistent data, which may be useful if retaining a history of request inputs and response outputs from consuming a SOAP service is important.
This is the model file: amortization.rb
require 'soap/wsdlDriver'
class Amortization < ActiveRecord::Base
def amortization
@amortization ||= wsdl.create_rpc_driver.calculate principal, interest, num_payments
end
private
def wsdl
SOAP::WSDLDriverFactory.new(url)
end
end
This is the controller file: amortizations_controller.rb
class AmortizationsController < ApplicationController
def new
@amortization = Amortization.new :url => 'http://www.kylehayes.info/webservices/AmortizationCalculator.cfc?wsdl'
end
def create
@amortization = Amortization.new params[:amortization]
if @amortization.save
redirect_to @amortization
# redirect_to :action => 'index'
else
render :action => 'new'
end
end
def show
@amortization = Amortization.find params[:id]
end
def index
@amortizations = Amortization.find(:all)
end
def delete
@amortizations = Amortization.find(params[:id])
@amortizations.destroy
redirect_to :action => 'index'
end
end
Below are the views in the views/amortizations folder.
Beginning with new.html.erb
<h1>Consume a SOAP Web Service</h1>
<% form_tag :action => 'create' do %>
<p>
<label for="amortization_WSDL_URL">Amortization WSDL URL</label><br/>
<%= text_field 'amortization', 'url', :value => @amortization.url %>
</p>
<p>
<label for="amortization_principal">Principal Amount</label><br/>
<%= text_field 'amortization', 'principal' %>
</p>
<p>
<label for="amortization_interest">Interest (not in percent)</label><br/>
<%= text_field 'amortization', 'interest' %>
</p>
<p>
<label for="amortization_num_payments">Number of Payments</label><br/>
<%= text_field 'amortization', 'num_payments' %>
</p>
<p>
<%= submit_tag "Store inputs to Amortization calculator" %>
</p>
<% end %>
<%= link_to 'Back', {:action => 'index'}%>
show.html.erb
<p>
<b>SOAP Web Service Response:</b> $
<%=h @amortization.amortize %>
</p>
index.html.erb
<h1>Listing services</h1>
<table>
<tr>
<th>Amortization per period - click URL to calculate</th>
<th>Principal Amount</th>
<th>Interest Rate (not in percent)</th>
<th>Number of Payment periods</th>
</tr>
<% if @amortizations.blank? %>
<p>no amortizations in system</p>
<% else %>
<% @amortizations.each do |a| %>
<tr>
<td><%= link_to a.url, a -%></td>
<td><%=h a.principal -%></td>
<td><%=h a.interest -%></td>
<td><%= link_to a.num_payments, {:action => 'show', :id => a.id} -%></td>
<td><%= link_to 'Delete', {:action => 'delete', :id => a.id}, :confirm => 'Are you sure?' %></td>
</tr>
<% end %>
<% end %>
</table>
<br />
<%= link_to 'New amortization', {:action => 'new'}%>
You will need to add the following to the routes file found in config/routes.rb.
map.resources :amortizations
Also, create a migration for creating amortizations table in the database using the file below and run
rake db:migrate
class CreateAmortizations < ActiveRecord::Migration
def self.up
create_table :amortizations do |t|
t.column :url, :string
t.column :principal, :decimal
t.column :interest, :decimal
t.column :num_payments, :decimal
t.timestamps
end
end
def self.down
drop_table :amortizations
end
end
In the next code sample, the data is not persistent and Amortization is not a subclass of Active Record. This could be useful, for example, when calling authentication methods of a webservice where you would not want to store the username/password. Thanks to the people in the Ruby on Rails IRC channel, the first method only needed minor modifications. Check out the channel #rubyonrails on irc.freenode.net.
The model, amort.rb
require 'soap/wsdlDriver'
class Amort
attr_accessor :url, :principal, :interest, :num_payments
def initialize(options = {} )
self.url = options[:url]
self.principal = options[:principal]
self.interest = options[:interest]
self.num_payments = options[:num_payments]
end
def data
%w(url principal interest num_payments).inject({}) do |acc, method|
acc.merge! method.to_sym => send(method)
end
end
def print_response
@soap = wsdl.create_rpc_driver
response = @soap.calculate(principal, interest, num_payments)
return response
end
private
def wsdl
SOAP::WSDLDriverFactory.new(url)
end
end
The controller, amorts_controller.rb
class AmortsController < ApplicationController
def new
@amort = Amort.new :url => 'http://www.kylehayes.info/webservices/AmortizationCalculator.cfc?wsdl'
end
def create
@amort = Amort.new params[:amort]
redirect_to url_for(:action => :show, :amort => @amort.data)
end
def show
@amort = Amort.new params[:amort]
end
end
new.html.erb
<h1>Consume a Web Service</h1>
<%= form_tag :action => 'create' %>
<p>
<label for="amort_WSDL_URL">Amortization WSDL URL</label><br/>
<%= text_field 'amort', 'url', :value => @amort.url %>
</p>
<p>
<label for="amort_principal">Principal Amount</label><br/>
<%= text_field 'amort', 'principal' %>
</p>
<p>
<label for="amort_interest">Interest (not in percent)</label><br/>
<%= text_field 'amort', 'interest' %>
</p>
<p>
<label for="amort_num_payments">Number of Payments</label><br/>
<%= text_field 'amort', 'num_payments' %>
</p>
<p>
<%= submit_tag "Create" %>
</p>
<%= link_to 'Back', amorts_path %>
show.html.erb
<p>
<b>The amortized payment per period is:</b>
<%=h @amort.print_response %>
</p>
<%= link_to 'Edit', edit_amort_path(@amort) %> |
<%= link_to 'Back', amorts_path %>
Don't forget to add a RESTful route for amorts into the routes.rb file. There is extra code in this sample, ie. the create method for the controller is not necessary and could be bypassed without a loss of functionality simply by changing the
:action => 'create'
in the new template to :action => 'show'
Other resources:
Another example
Ruby Standard Library Documentation (RDoc)
The full source of SOAP4R with examples is available at the "trac"-ing site
IRC:
If you want to use a controller method in a view, in the controller you need to specify helper_method :use_web_service, then you can call use_web_service in your views directly. That won't pass in a params hash, though, so you're better off creating a method that takes an array of arguments, and calling that from your use_web_service action and your views.
[code]Try at refactoring... http://pastie.org/398113[/code]
Comments