ServiceBrowser: browsing your rails controllers

Christof Müller Dieser Artikel wurde von geschrieben. Christof Müller ist Software-Entwickler bei ubigrate. Er entwickelt schwerpunktmäßig Serverkomponenten im Technologiebereich zwischen Java und Ruby (on Rails).

The more complex your amf-speaking Flex app, the more painful and time consuming the act of testing and debugging your Rails controllers gets. Even if testing a specific controller method via the Flex gui only takes you a couple of clicks, it adds up. Plus, if you want to investigate how your controller reacts to unexpected, even invalid parameters, you’ll end up spending your day editing Flex code, recompiling, launching and clicking around in your Flex app – although you actually want to test your controllers. That’s bad. The issue gets even more severe if you consider the fact that Rails’ integrated functional testing environment doesn’t work with pure amf (see this thread for a very tentative try to get it working).

Luckily, over at amfphp.org, they offer an interesting tool named ServiceBrowser, which is essentially a tiny Flex application and, at the server’s end, a corresponding DiscoveryService. The DiscoveryService reflects on every controller’s exposed methods and delivers method tables to the Flex client. The latter lets you in turn invoke each of these methods and inspect the results with no more than a single click. You can even send arbitrary parameters – as long as they can be expressed in JSON (which is the case for at least Strings, Numbers, Arrays and Hashes). When porting the DiscoveryService to Rails, there is one limitation, though: Since parameters are passed implicitly via the params hash, there is no (obvious) way for the DiscoveryService to determine the number and types of parameters a certain method expects. This might be problem (depending on your controllers) but you can work around it.

The following short tutorial is on how to imitate the AMFPHP DiscoveryService on a Rails app and adapt the AMFPHP ServiceBrowser in order to make it work with RubyAMF. So, with little effort you can get a neat little helper for developing a Rails/RubyAMF/Flex app.

Prerequisites: a working Ruby on Rails app with RubyAMF gateway up und running, Flex compiler mxmlc, latest AMFPHP release (currently 1.9beta2)

Set up the controller

First of all, we need to create the controller that acts as pendant to AMFPHP’s DiscoveryService, let’s name it DiscoveryController, with respect to Rails’ conventions. Do

  ruby script/generate controller Discovery

and enter the following code into the newly created app/controllers/discovery_controller.rb

class DiscoveryController < ApplicationController
  def get_services
    controllers = Dir.entries('./app/controllers')
    controllers -= %w(. .. application.rb discovery_controller.rb rubyamf_controller.rb)
    ret_val = []
    controllers.each do |elem|
      elem = File.basename(elem, '.rb').to_camel!
      elem = elem.first(1).upcase + elem.last(-1)
      ret_val << {'label' => elem, 'address' => elem}
    end
    render :amf => ret_val
  end
  alias_method :getServices, :get_services 

  def describe_service
    ret_val = []
    if eval(rubyamf_params[0][:address]).ancestors.include? ApplicationController
      ret_val = eval(rubyamf_params[0][:address]).simple_method_table
    end
    render :amf => ret_val
  end
  alias_method :describeService, :describe_service
end

The script consists of two methods which will be called by the Flex client. The first, “get_services”, returns all available controllers except a few we wouldn’t want to explore (note that we can’t use subclasses_of here, since it doesn’t work within modules). The second method, “describe_service”, returns a more or less detailed list of methods for a certain controller. Since RubyAMF converts pretty much everything that runs through its fingers from camel case to snake case and vice versa, we define two alias methods to cover that. In case you are wondering why you haven’t heard of the method “simple_method_table” before: That’s because it doesn’t exist – yet. We change this now.

Extending ActionController::Base

Create a file with an arbitrary name and save it to some place where Rails finds it. For example, create a file “method_table.rb” in the lib directory of your app. Put the following code in:

  class << ActionController::Base
    def simple_method_table
      ret_val = {}
      method_list = eval(self.to_s).public_instance_methods(false)
      method_list.each do |m_name|
        ret_val[m_name] = {}
        ret_val[m_name]['name'] = m_name
        ret_val[m_name]['arguments'] = ['arg']
        ret_val[m_name]['description'] = 'not yet'
      end
      ret_val
    end
  end

This code extends the ActionController base class by the class method “simple_method_table”. That is, every ActionController and any of its derived classes can use this method. The purpose of “simple_method_table” is to collect all declared instance methods along with their accepted arguments and a description. Next, make sure that “method_table.rb” is loaded when the DiscoveryController gets called. To accomplish this, put the following line at the very top of “discovery_controller.rb”:

  require "method_table"

That’s it for the Rails part. Next we have to slightly modify the ServiceBrowser provided by AMFPHP and get rid of some AMFPHP specific stuff.

Modify AMFPHP ServiceBrowser

Navigate to your copy of AMFPHP, subdirectory “Browser”. Edit “services-config.xml” and change the endpoint of the channel definition (about line 20) to point at your RubyAMF gateway. For example, if your Rails server runs at localhost, port 3000, the endpoint should read like this:

  <endpoint uri="http://localhost:3000/rubyamf/gateway" class="flex.messaging.endpoints.AMFEndpoint"/>

In “servicebrowser.mxml”, line 47, change “gateway.php” to “rubyamf/gateway” and modify line 160 to

  <mx:RemoteObject showBusyCursor="true" destination="amfphp"
    endpoint="http://localhost:3000/rubyamf/gateway"
    source="DiscoveryController" id="ro">

One last change is in line 81, which starts with:

  sro.source = currentService.data.split('/')...snip

It should be changed to:

  sro.source = currentService.label;

Compile your modified application by running mxmlc on “servicebrowser.mxml” and you’re done:

  mxmlc --services="services-config.xml" servicebrowser.mxml

Browsing your controllers

If your Rails server is running, you can start servicebrowser.swf and should see something like that, depending on what controllers exist in your Rails app.

screen_simple7

Passing parameters

You certainly noticed that the “simple_method_table” method above uses two hard coded values for “arguments” and “description”. This results in every method being described by “not yet” and having exactly one argument named “arg”. This argument will be passed to the controller in rubyamf_params[0], which is fine for most simple cases (Remember: if you send a hash here, everything is exactly like receiving a html form). If any controller method expects more than one argument (e.g. in rubyamf_params[1]), you’ll need another input field, obviously. And you might want to have a more meaningful description than “not yet”. This is where you need to place comments/annotations into the source code and either manually scan the controller files with a little RegExp or take advantage of IHelp. Figuring out a sophisticated way to do this is left as an exercise for the reader.

Thanks for reading. Any comments and questions are welcome!

Post to Twitter

Schlagworte:

Hinterlasse einen Kommentar

Name:

eMail:

Website:

Kommentar:

 

google

google

asus