Rails Layered Dispatching Hints
For my current Rails project I’m setting up my web services with layered dispatch, which nicely organizes all the methods while keeping them at the same endpoint URI. There’s some good documentation already out there in the Agile Web Development with Rails book as well as the ActionWebServices manual, but I have a few extra tips that could be helpful.
If we’re using a before_invocation interceptor, like say for authentication, we can clean things up by creating a subclass of ActionWebService::Base that includes the authentication method:
app/apis/authenticated_web_service.rb
class AuthenticatedWebService < ActionWebService::Base
def authenticate name, args
@authenticated_user = User.find_by_login args[0]
unless @authenticated_user.authenticated?(args[1])
raise "Not authenticated"
end
end
end
Because we are actually using an instance variable here, @authenticated_user, we have to set up our layered web service controller to use instances of the services rather than the classes.
app/controllers/services_controller.rb
class ServicesController < ApplicationController
web_service_dispatching_mode :layered
wsdl_service_name 'contract_services'
web_service_scaffold :invoke
# Here we use instances...
web_service(:contract) {ContractService.new}
web_service(:account) {AccountService.new}
# rather than classes...
# web_service :contract, ContractService
# web_service :account, AccountService
end
Then your various services can inherit from this “abstract” AuthenticatedWebService. Each can make use of the authenticate method and the @authenticated_user variable.
app/apis/contract_service.rb
class ContractApi < ActionWebService::API::Base
api_method :all,
:returns => [[Contract]]
api_method :new,
:expects => [{:login => :string},
{:password => :string},
{:description => :string}],
:returns => [Contract]
end
class ContractService < AuthenticatedWebService
web_service_api ContractApi
def all
Contract.find :all
end
def find contract_id
c = Contract.find(contract_id)
raise "Contract not found." if c.nil?
c
end
## Authenticated API Methods
before_invocation :authenticate, \:only => [:new]
def new l, p, description
c = Contract.new :creator => @authenticated_user,
:description => description
c.save!
c
end
end
Once your service is put together, keep in mind that XML-RPC requests will use a method of the form service.method. So for example, my contract service’s find method is contract.find. I have to admit that this really threw me off for a little while when I was testing out the API in XML-RPC Client and I kept getting back a “no such web service ‘api’” error message. The manual does point this out, but I missed it the first time, and only on a close reread did I figure this out, at which point I realized how much good sense this approach makes. Note that for SOAP requests the service API is encoded in the header.
One more final note is that Rails logger object is not available in the layered services. This could be a deal breaker in certain cases.

