The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Lifting typo onto ActionWebService

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Patrick Lenz

Posts: 168
Nickname: scoop
Registered: Apr, 2005

Patrick Lenz is the lead developer at freshmeat.net and a contributor to the typo weblog engine
Lifting typo onto ActionWebService Posted: Apr 11, 2005 2:22 AM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Patrick Lenz.
Original Post: Lifting typo onto ActionWebService
Feed Title: poocs.net
Feed URL: http://feeds.feedburner.com/poocsnet
Feed Description: Personal weblog about free and open source software, personal development projects and random geek buzz.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Patrick Lenz
Latest Posts From poocs.net

Advertisement

I've been working on getting typo moved over to Rails' ActionWebService, a SOAP and XML-RPC framework, that has been added with Rails 0.10.

Since this is still a pretty young incarnation I encountered a few pitfalls on the way that I'd like to share for the sake of improving the experience other developers might have. A good starting point for doing new development using ActionWebService is the AWS book.

The controller

ActionWebService (AWS) has three different operating modes. Direct, delegated, and layered. The options differ only in the fact of which class is responsible for implementing the actual service methods. Direct, as the name implies, requires the controller itself to implement all methods. Delegated, on the other hand, involves a separate class for this job. Layered is similar, although it allows for a single controller to take responsibility for several service classes. Detailed schemas are available in the AWS book.

As a side note, the layered approach only works with XML-RPC, the other two work with both XML-RPC and SOAP (transparently for you, the application developer).

Since typo's backend involves three different blogging APIs (Blogger, metaWeblog and MovableType) the only option was to use the layered approach. That way I was able to "mount" the different services into the existing backend_controller:

class BackendController < ApplicationController

  web_service_dispatching_mode :layered
  web_service(:metaWeblog) { MetaWeblogService.new(@request) }
  web_service(:mt) { MoveableTypeService.new }
  web_service(:blogger) { BloggerService.new(@request) }

end

The service API

For easy error checking both client and server-side, AWS requires you to define the API for your webservice as detailed as possible. Every method is declared with a specific method signature, including the parameters (and their type of value) and the return value (and its type of value) in a class derived from ActionWebService::API::Base.

class MetaWeblogApi < ActionWebService::API::Base

  api_method :getCategories,
    :expects => [ {:blogid => :int}, {:username => :string},
                  {:password => :string} ],
    :returns => [[:string]]

end

AWS also supports Rails' standard inflection on names, which means you should actually be able to use a definition of api_method :get_categories and still be able to use it as getCategories via the API. However, this didn't work as expected so I decided to drop that idea for now and possibly file a bug with the framework at a later point. To turn off the inflector you simply put inflect_names false at the top of the API header.

More complex parameters and return values

Most APIs involve structs as means to transmit data which is more complex than a single string or int. Structs are actually comparable to regular Hash types but with the added benefit that you can (and probably have to) spec out the key => value pairs.

To do this I setup a module for all structs involved in the API declaration.

module MoveableTypeStructs

  class ArticleTitle < ActionWebService::Struct
    member :dateCreated,  :string
    member :userid,       :string
    member :postid,       :string
    member :title,        :string
  end

end

This module definition can now be reused as part of the API declaration of an API method.

class MoveableTypeApi < ActionWebService::API::Base
  inflect_names false

  api_method :getRecentPostTitles,
    :expects => [ {:blogid => :int}, {:username => :string},
                  {:password => :string}, {:numberOfPosts => :int} ],
    :returns => [[MoveableTypeStructs::ArticleTitle]]

end

Reading out loud, this method returns an Array of the just defined struct MoveableTypeStructs::ArticleTitle. Structs just can as well be used for specifying parameters.

api_method :newPost,
  :expects => [ {:blogid => :int}, {:username => :string},
                {:password => :string},
                {:struct => MetaWeblogStructs::Article},
                {:publish => :int} ],
  :returns => [:int]

The actual service implementation

We added hooks to the controller to point to a service which we have yet to implement. This is the actual workhorse for AWS, the class where the methods themselves are implemented. Service classes are derived from ActionWebService::Base.

class BloggerService < ActionWebService::Base
  web_service_api BloggerApi

  def deletePost(appkey, postid, username, password, publish)
    raise "Invalid login" unless valid_login?(username, password)
    article = Article.find(postid)
    article.destroy
    true
  end

end

As you can see, while you map your services to your controller using a call to web_service in the controller definition, you map the API to your service using a call to web_service_api. Makes sense, doesn't it?

The naming conventions are pretty lax btw. You're free to uppercase API or CamelCase it or do whatever you please. It does make most sense to prefix both API, services, and structs modules with a common basename though.

When looking at the actual methods used in typo's AWS incarnation they differ only slightly from the ones Tobias originally developed before AWS was available. This actually means that methods don't have to change just because you're changing the surrounding methodology. The main things changed involved replacing ordinary arrays and hashes by using the more specific struts where appropriate.

Where do I put all of this?!

That's what puzzled me a bit at first. Rails 0.10 shipped with a new app/apis subdir. This is the place your services and APIs live in. In order to be able to just work your way through the code without splattering require and load statements everywhere (read: The Rails Way), just name your files after your services class since this is the first thing you specify from the "outside" (remember the web_service call in the controller definition? That's the point where Rails tries to include a not-yet-loaded file by inflecting on the classname, e.g. MetaWeblogService.)

In typo's case there are:

blogger_service.rb
meta_weblog_service.rb
moveable_type_service.rb

Now, how do I access it?

The API docs mention this. Somewhere. Along the way. I misread it. Or didn't read it at all. And I wasted precious time hunting that one down, which is why I'm giving it some emphasis here:

For this example, protocol requests for Add and Remove methods sent to /person/api will be routed to the actions add and remove.

The default pseudo-method to access your freshly developed web services API is /controller_name/api. If you try to leave off the api part you end up with a controller complaining about a missing index action.

Since I didn't feel like breaking backwards compatibility with my first major contribution to typo, I simply aliased that default method to the existing xmlrpc method in the controller definition.

  alias xmlrpc api

There, done.

Testing!

ActionWebService integrates nicely with the Rails Testing Framework. Basically, if you start doing your web service by using a generator like this:

# script/generate web_service Blogger deletePost getUsersBlogs

.. you end up with the usual test stubs for you to fill out in test/functional/blogger_api_test.rb.

As you cannot issue requests directly (you're not running a webserver when testing, do you?), you have to go through the methods provided by test_invoke. For the layered approach in typo, I've been using invoke_layered to properly test all parts of the API.

  def test_mt_get_post_categories
    article = Article.find(1)
    article.categories << @software

    args = [ 1, 'tobi', 'whatever' ]

    result = invoke_layered :mt, :getPostCategories, *args
    assert_equal result.first['categoryName'],
                 article.categories.first['name']
  end

One thing that wasn't crystal clear to me from either the documentation or the examples is that you're not supposed to pass hashes or anything to invoke_layered, but plain old arrays. XML-RPC per se doesn't use named parameters at all, so this approach probably makes sense. Just keep in mind that you pass in the args array in the order declared in the method definition in your service class.

Summary

What is the gain of using AWS over the by-foot implementation typo had before?

First of all, it allows for Test Driven Development. Being able to back your changes by proper functional testing will result in less error prone software as well as less surprises that a single change somewhere completely unrelated breaks something elsewhere.

Additionally, AWS takes over sanitizing client input and makes sure your methods receive proper values before requests even get to them.

What's left?

typo's AWS incarnation currently lives in a separate branch while we isolate the remaining bugs. too-biased and this blog already run this code to prove it mature enough to merge it back to trunk. We do have an item on our AWS wishlist though, which we'd like bitserf to implement before we release a new typo version: Getting easy access to the controller instance from within the service class, particularly url_for. Generating URLs within services is a tedious and ugly task right now and I won't quote the code here on purpose ;)

What's left for you? Well, typo is Open Source and as such you can check out the complete code I've been talking about back and forth in this article using its trac instance or check it out from the subversion repository.

Read: Lifting typo onto ActionWebService

Topic: Say bye to /articles/read/5 Previous Topic   Next Topic Topic: Nobu Grades My Homework

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use