Part 1: Using WebORB to access ActiveRecords from a Flex application. 5

Posted by Daniel Wanja Sun, 29 Oct 2006 20:21:00 GMT

On Friday I started for a customer an investigation in providing a Flex front-end for an Ruby on Rails backend using WebORB. In parallel I will push this investigation further for myself in order to find a nice mechanisms to support CRUD operations with relationship support using WebORB. Over the next couple of weeks I will write some of my findings on this blog. So this week-end I started to put in place an environment where I can unit test the interaction between Flex and Ruby on Rails using WebORB. In this first part I will show how to extend WebORB to perform a deep find, how to write a Flex unit test to test asynchronous remote calls, and how to use Ruby on Rails fixtures for the Flex unit tests.

This is an extract of the ‘final’ version of the Flex unit test (as of Part 1 of the article). The full version is at the end of the article.

public function testGetFirstCustomer():void {
    var activeRecordService:RemoteObject = getActiveRecordService(onGetFirstCustomerResult); 
    create_fixtures(["customers", "addresses", "orders", "items"], doGetCustomerFirstCustomer, activeRecordService);
}          
private function doGetCustomerFirstCustomer(activeRecordService:Object):void {
    var options:Object = {'include':['bill_to_address', {'orders':'items'}]};
    activeRecordService.get("Customer", 1, options);                       
}
private function onGetFirstCustomerResult(event:Event, token:Object=null):void 
{
    assertTrue(event.toString(), event is ResultEvent); // First param is message.
    var customer:Object = ResultEvent(event).result;
    assertEquals("Daniel", customer.name);
    assertEquals("Littleton", customer.bill_to_address.city);
    assertEquals(2, customer.orders.length);    // 2 order
    assertEquals(3, customer.orders[0].items.length); // the first has 3 items
    assertEquals("Remote Control", customer.orders[0].items[2].product); 
}          
          

Introduction

There are several ways a Flex front-end can connect to a Rails application: via a RESTFul services (xml over html), via SOAP, and now also using WebORB from Midnight Coders (see http://www.themidnightcoders.com/weborb/rubyonrails/index.htm). WebORB for Ruby on Rails is a server-side technology enabling connectivity between Flex and Flash Remoting clients and Ruby on Rails applications. WebORB and Flex provides an efficient way of encoding and decoding data using a binary protocol named AMF3 (Actionscript Message Format (I think)). This format is recognized by the Flash Player and provides an efficient way to communicate between a Flex application and a Ruby On Rails server.

This article will only investigate the WebORB usage, and we will not look into using SOAP or a RESTFul api. Part 1 of the article will show how to setup the Rails and the Flex applications to use WebORB, and how to retrieve data from the server. In subsequent parts of this article, I will comment my findings regarding updating data, security and performance issues and other aspect I discover during my investigation.

As you may not know, with Lee we wrote a similar server-side technology 18 months ago (flexonrails.com) which was based on AMF4R, but we where not satisfied with our findings at that time, nor with the qualitify of one of the library we where using, and the demand for integrating Flex and Ruby On Rails was non existent at that time, certainly due to the high price tag of Flex and the fact that not many developers were interested in both of these technologies as Flex was geared towards the enterprise and RoR was an open source framework. Since then Flex is free (not FlexBuilder, nor the DataServices), but everything we do in this article can be developed and deployed using free tools. Also Adobe is pushing quite hard to appeal to the open source community. I started this project using FlexBuilder for Mac, but I will also show how to compile the samples and tests using the flex command line compiler (mxmlc).

To download WebORB and to set it up I am following the instructions of http://www.themidnightcoders.com/weborb/rubyonrails/gettingstarted.htm. So for more details go see www.themidnightcoders.com

For this article I will create a “fictional” application. For my customer I am directly integrating with the existing application. In fact using this fictional application will allow me to test the WebORB in many different ways. Note that my customers current front-end application is really cool. It is written in plain-old Rails, aka Ajax, html, css, rjs. Nevertheless we wanted to investigate if we can achieve similar functionality with less code using Flex and to identify the cost and benefits of such a development. I will spend a couple of days over the next weeks for this investigations.

These are the steps I followed for this article

1. Create a Rails app and add the WebORB plugin
2. Write a simple unit test to access some data using a RemoteObject 
    a. create the Flex application using FlexBuilder.
         b. create the Customer ActiveRecord
         c. Write the FlexUnit test
    d. Write a small script to compile a Flex app from the command prompt.
         e. make the test pass.
3 . Use fixtures
4. Create a model (db+active record)
5. Write a test to retrieve the model
6. Provide a way to use fixtures from FlexUnit, so we can do some more testing.

1. Create a Rails app and add the WebORB plugin

* Go to the folder where you want create your rails application and issue the following command:
    rails rails

This create the rails application in a folder named “rails”. You can choose any name you want i.e. rails myApp. Note we use rails 1.1.6 for this article.

* Cd to the root of you rails application and install the plugin. The following command installs the plugin files in the vendor/plugins/weborb folder.

./script/plugin install http://themidnightcoders.net:8089/svn/weborb

2. Write a simple unit test to access some data using a RemoteObject

We will put the flex applications in a folder named “flex” and put this folder next to the “rails” folder we create just before. Now lets download FlexUnit from http://weblogs.macromedia.com/as_libraries/zips/flexunit.zip. See Adobes wiki for more information (http://labs.adobe.com/wiki/index.php/ActionScript_3:resources:apis:libraries#FlexUnit)

2.a) Creating a project in FlexBuilder

* Create a FlexBuilder project (select Flex Data Services)
Root Folder: /Users/daniel/MyStuff/Projects/WebORBInvestigation/rails/config
Root Url: http://localhost.com:3000/weborb/

Root Folder points to your Rails application config folder
Root Url points to the weborb controller.

* In the project properties set
Output folder: /Users/daniel/MyStuff/Projects/WebORBInvestigation/rails/public/flex
Output folder URL: http://localhost:3000/flex

  • Add the flexunit.swc (in the /bin folder of the FlexUnit downloads). A .swc is the compiled version of the FlexUnit framework.

2.b) create the Customer ActiveRecord

    ./script/generate model Customer
This generate the following files:
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/customer.rb
      create  test/unit/customer_test.rb
      create  test/fixtures/customers.yml
      create  db/migrate
      create  db/migrate/001_create_customers.rb
Now edit db/migrate/001_create_customers.rb as follows:
class CreateCustomers < ActiveRecord::Migration
  def self.up
    create_table :customers do |t|
      t.column "name", :string
      t.column "address", :string
    end
  end

  def self.down
    drop_table :customers
  end
end
Now make sure you created an empty database and you point to it in the config/database.yml file. Then create the customers table by typing the following command:
    rake migrate
Now let’s create two customers:
./script/console
>> Customer.create(:name => "Daniel", :address => "Denver") 
>> Customer.create(:name => "Samuel", :address => "Geneva")
>> exit

2.c) Write a flex unit test

One of the issues with Flash and Flex unit testing of remote calls is the asynchronous nature of the remote calls. FlexUnit was based on JUnit and was mainly created for synchronous call testing. On one large project we dealt with this issue by splitting each test in two phases. The first phase performing calls and collecting data, we called it CallSequence, and the second phase to test the returned data. Since then, FlexUnit was extended with a nice feature allowing to test asynchronous calls. Look for the usage of the TestCase.addAsync method. It allows to notify that the current test method is waiting for a callback.

With this knowledge in mind let’s dive into writing the test. We want to retrieve the list of customers, ensure that their are two customers, and verify the name off one of the customers. This is what the test looks like:

WebORBReadOnlyTest.as
package {
     import flexunit.framework.TestCase;
    import mx.rpc.remoting.RemoteObject;
    import flash.events.Event;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.events.FaultEvent;     

     public class WebORBReadOnlyTest extends TestCase {

          public function WebORBReadOnlyTest( methodName:String = null ) {
               super( methodName );
        }

        // This method triggers a remote call and defines the event handler for the asynchronous response.
          public function testGetCustomerList():void {
              var asyncCall:Function = addAsync(onCustomerListResult, 1000);
             var dataService:RemoteObject = new RemoteObject();   
            dataService.destination = "DataService"; // Default WebORB ActiveRecord accessor 
            dataService.addEventListener("result", asyncCall);
            dataService.addEventListener("fault", asyncCall);
             dataService.list("Customer");        
           }          
           // This is the even handler trigger by the asynchronous response, let assert that we received what we expected.
        private function onCustomerListResult(event:Event, token:Object=null):void 
        {
            assertTrue(event is ResultEvent); 
            var data:Object = ResultEvent(event).result;
            assertEquals(2, data.length);
            assertEquals("Daniel", data[0].name);
        }

      }
}

We also need a test runner

WebORBTestApp.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="absolute"
                xmlns:flexunit="flexunit.flexui.*"
                creationComplete="runTests()">
    <mx:Script>
        <![CDATA[
            import flexunit.framework.TestSuite;

            private function runTests():void
            {
                 var ts:TestSuite = new TestSuite();                 
                 ts.addTestSuite( WebORBReadOnlyTest );                 
                testRunner.test = ts;
                testRunner.startTest();
             }
        ]]>
    </mx:Script>

    <!-- flexunit provides a very handy default test runner GUI -->
    <flexunit:TestRunnerBase id="testRunner" width="100%" height="100%" />

</mx:Application>

2.d) Compile the unit test

The following script compiles the application from the command line (on OSX). That is, not using FlexBuilder. You may need to adapt for your needs. On windows it may be easier and you can maybe just refer to the mxmlc.exe. Not that I had to patch the flex-config file due to an compilation issue due to some missing fonts. You can try without the patch first. Let me know if that worked.

build.sh
FLEX_HOME="/Applications/Adobe Flex Builder 2 Beta/Flex SDK 2/"
cp flex-config-patched.xml "$FLEX_HOME/frameworks/"
java -Xmx384m -Dsun.io.useCanonCaches=false -jar "$FLEX_HOME/lib/mxmlc.jar" -load-config "$FLEX_HOME/frameworks/flex-config-patched.xml"  -library-path+=../flexunit/bin/flexunit.swc WebORBTestApp.mxml
cp WebORBTestApp.swf ../../rails/public/flex 

2.e) Run the unit test

Make sure the server is running and launch the flex test application.

Note that I had some red bars before I got the test to pass, but I wanted to spare these details.

Ok, this is quite some work just do test the dataService.list(“Customer”) call. However now I have an environment which I can use to go crazy on testing various aspects of the Flex and Ruby On Rails integration using WebORB.

3 . Extend WebORB to allow to write a FlexUnit test using fixtures

Without fixtures this test wouldn’t be very useful. We need a way to reset the fixtures between each test method invocation.

3.a) First let’s add a mechanism to reset the fixtures using WebORB

create the app/services/ActiveRecordService.rb file (note this is the naming convention for the WebORB services, I would rather use active_record_service.rb but I haven’t tried if that works.)

require 'weborb/context'
require 'active_record/fixtures'
class ActiveRecordService 
  def create_fixtures(table_names)
    Fixtures.create_fixtures("#{RAILS_ROOT}/test/fixtures/", table_names, {})
  end 
end    
ActiveRecordService is the placeholder where we will extend the standard WebORB DataService offering, by adding some utility methods to reset the fixtures and by providing an extended get method as described further in this document.

Now configure WebORB to recognize this new service. Add the following code to the config/WEB-INF/flex/remoting-config.xml file

    <destination id="ActiveRecordService">
        <properties>
            <source>ActiveRecordService</source>
        </properties>
    </destination>

A server restart is required when changing ActiveRecordService. When invoking create_fixtures this reloads the fixture based on the provided table names, however it overwrite the tables content of the database mode (development, test or production) for which you server is currently running in. I use the development mode during this exercise, so my weborb_development tables will be loaded with the fixtures.

3.b) Now let’s invoke the create_fixtures from our flex unit test.

Warning, I haven’t found a nice way (yet) to transparently invoke the create_fixtures from the setup of the test. So for now each test method we will have three methods 1) that notifies that the test method is asynchronous and invokes the create_fixtures methods, 2) the callback of the create fixtures that will perform the ‘real’ remote call we want to perform, and 3) the callback of the ‘real’ remote call to perform the assertions. That’s when you wish that Flex supports directly synchronous calls. However this can be wrapped way nicer than I just did. I will certainly refactor this once I need to write more tests.

Edit the rails/test/fixtures/customers.yml as follows:

first:
  id: 1
  name: Daniel
  address: Littleton
another:
  id: 2
  name: Samuel
  address: Bernex

The test now looks as follows:

package {
     import flash.events.Event;

     import flexunit.framework.TestCase;

     import mx.rpc.events.FaultEvent;
     import mx.rpc.events.ResultEvent;
     import mx.rpc.remoting.RemoteObject;
     import mx.rpc.AsyncToken;     
    import mx.collections.ItemResponder;
    import mx.controls.Alert;

     public class WebORBReadOnlyTest extends TestCase {

          public function WebORBReadOnlyTest( methodName:String = null ) {
               super( methodName );
        }

        // The mechanism to invoke the server side create_fixtures method is still a little clunky here.
        // This should be part of the setUp method, which doesn't support asynchronous setUp for now.
        // So writing a test with fixture now requires three method (that's the really clunky part!)
        //     1. the testXXXXX method that must call the getActiveRecordService and the create_fixture method.
        //     2. the method that will actual call the server (here. doGetCustomerList)
        //     3. the result handler for the abort call that will perform the assert.
          public function testGetCustomerList():void {
             // getActiveRecordService - Signal to TestCase that this is an asynchronous test
             // and pass end point of the test method (in this case the onCustomerListResult method)
             var activeRecordService:RemoteObject = getActiveRecordService(onCustomerListResult); 
              create_fixtures(["customers"], doGetCustomerList, activeRecordService);
           }          
           private function doGetCustomerList(activeRecordService:Object):void {
             activeRecordService.list("Customer");                       
           }
           // This is the even handler trigger by the asynchronous response, let assert that we received what we expected.
        private function onCustomerListResult(event:Event, token:Object=null):void 
        {
            assertTrue(event.toString(), event is ResultEvent); // First param is message.
            var data:Object = ResultEvent(event).result;
            assertEquals(2, data.length);
            assertEquals("Daniel", data[0].name);
            assertEquals("Littleton", data[0].address);
        }

        private function getActiveRecordService(resultHandler:Function, timeOut:Number=2000):RemoteObject 
        {
              var asyncCall:Function = addAsync(resultHandler, timeOut);
             var activeRecordService:RemoteObject = new RemoteObject();   
            activeRecordService.destination = "ActiveRecordService";
            activeRecordService.addEventListener("result", asyncCall);
            activeRecordService.addEventListener("fault", asyncCall);
            return activeRecordService;            
        }

        private function create_fixtures(table_names:Array, callback:Function, callbackData:Object):void 
        {
             var activeRecordService:RemoteObject = new RemoteObject();   
            activeRecordService.destination = "ActiveRecordService";
             var call:AsyncToken = activeRecordService.create_fixtures(table_names);    
            call.addResponder(new ItemResponder(onCreateFixturesResult, onCreateFixturesFault));
            call.callback = callback;
            call.callbackData = callbackData;
        }
        private function onCreateFixturesResult(event:ResultEvent, token:Object=null):void 
        {
            event.token.callback(event.token.callbackData);
        }
        private function onCreateFixturesFault(event:Event, token:Object=null):void
        {
            fail("Failed to create fixtures. "+event.toString());
        }            

      }
}

4. Implementing a deep find

With the default WebORB’s DataService implementation you cannot extract a Customer and his relationships (we will add them shortly) in one call. However this is where the AMF protocol really shines as nested structure can be serialized over http in one call. This is similar to what XML could provide but allows strongly typed objects to be mapped between Flex and Ruby On Rails. The current Customer model is not very representative for an application, so let’s extend our model as follows:

I know you may want to argue why is the ship_to/bill_to on the customer and not on the order. Hey, it’s just for testing purpose for now, just to have some richer relationships between objects :-)

4.1 Extending the Model

the following command to generate the new models
 ./script/generate model Address 
 ./script/generate model Order --skip-migration  
 ./script/generate model  Item --skip-migration  
This creates the active records, the fixtures and the db/migrate/002_create_addresses.rb database migration file.

Edit 002_create_addresses.rb as follows and run ‘rake migrate’

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |t|
      t.column "street", :string
      t.column "zip", :string
      t.column "city", :string
    end
    create_table :orders do |t|
      t.column "customer_id", :integer
      t.column "created_at", :datetime
    end
    create_table :items do |t|
      t.column "order_id", :integer
      t.column "quantity", :integer
      t.column "product", :string
    end        
    remove_column :customers, :address
    add_column :customers, "bill_to_address_id", :integer
    add_column :customers, "ship_to_address_id", :integer
  end

  def self.down
    drop_table :addresses
    drop_table :orders
    drop_table :items
    add_column :customers, :address, :string
  end
end
And define the ActiveRecord relationships as follows:
class Customer < ActiveRecord::Base
  has_many :orders
  belongs_to :bill_to_address, :class_name => "Address", :foreign_key => "bill_to_address_id"
  belongs_to :ship_to_address, :class_name => "Address", :foreign_key => "ship_to_address_id"  
end
class Address < ActiveRecord::Base
  belongs_to :customer
end
class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :items
end
class Item < ActiveRecord::Base
  belongs_to :order
end
And the define some more fixtures:
customers.yml
first:
  id: 1
  name: Daniel
another:
  id: 2
  name: Samuel
addresses.yml
first:
  id: 1
  street: First Street.
  zip: 80246
  city: Littleton  
another:
  id: 2
  street: Other Avenue.
  zip: 1200
  city: Bernex  
orders.yml
hardware_order:
  id: 1
  customer_id: 1
  bill_to_address_id: 1
book_order:
  id: 2
  customer_id: 1
  bill_to_address_id: 1
items.yml
media_center:
  id: 1
  order_id: 1
  product: iTv
  quantity: 1
screen:
  id: 2
  order_id: 1
  product: 200" LCD Screen
  quantity: 1
remote:
  id: 3
  order_id: 1
  product: Remote Control
  quantity: 1
book1:
  id: 4
  order_id: 2
  product: Agile Web Development With Rails, 2nd Edition
  quantity: 1
book2:
  id: 5
  order_id: 2
  product: Mastering CSS
  quantity: 1

4.2 Add get to ActiveRecordService

The rails ActiveRecord.find method allows for the :include option to specify what relationship should be retrieved as part of the find. This will allow to perform 1 call and retrieve the customer, his address and all of his orders and order items.

Let’s extend ActiveRecordService as follows (we show only the new methods):

class ActiveRecordService 

  # Allow for a deep fetch (find_by_id with :include options)    
  def get(model_name, id, options={})
    model_class = Object.const_get model_name
    find_options = {:include => options['include']} # Here we limit options for now.
    result = model_class.send(:find_by_id, id, find_options)
    puts result.inspect
    result
  end  

  def find_all(model_name, options={})
    model_class = Object.const_get model_name
    find_options = {:include => options['include']} # Here we limit options for now.
    result = model_class.send(:all, find_options)
    puts result.inspect
    result
  end  
end

4.3 Call the new get method from our flex unit test

          public function testGetFirstCustomer():void {
             var activeRecordService:RemoteObject = getActiveRecordService(onGetFirstCustomerResult); 
              create_fixtures(["customers", "addresses", "orders", "items"], doGetCustomerFirstCustomer, activeRecordService);
           }          
           private function doGetCustomerFirstCustomer(activeRecordService:Object):void {
            var options:Object = {'include':['bill_to_address', {'orders':'items'}]};
             activeRecordService.get("Customer", 1, options);                       
           }
        private function onGetFirstCustomerResult(event:Event, token:Object=null):void 
        {
            assertTrue(event.toString(), event is ResultEvent); // First param is message.
            var customer:Object = ResultEvent(event).result;
            assertEquals("Daniel", customer.name);
            assertEquals("Littleton", customer.bill_to_address.city);
            assertEquals(2, customer.orders.length);    // 2 order
            assertEquals(3, customer.orders[0].items.length); // the first has 3 items
            assertEquals("Remote Control", customer.orders[0].items[2].product); 
        }  

Now that is getting more interesting. From flex in the doGetCustomerFirstCustomer method the following call activeRecordService.get(“Customer”, 1, options); returns the customer his addresses, his orders and all order items. It would be even nicer if we could call Customer.find(1, options).

Comments

Leave a response

  1. Brad Tue, 31 Oct 2006 08:43:20 GMT

    i love the article on weborb and ruby!

    keep up the great work, i look forward to seeing your future posts.

  2. Stuart Thu, 21 Dec 2006 17:37:59 GMT

    I wish you would have not spared the details about the red bars. My tests are failing.

  3. Daniel Wanja Thu, 21 Dec 2006 19:45:21 GMT

    Hi Stuart,

    Please send me the details of your error here or to daniel@onrails.org. I could maybe point you in the right direction.

  4. Stuart Fri, 22 Dec 2006 15:38:45 GMT

    sent you an email.

  5. Ajay Sun, 11 May 2008 12:29:11 GMT

    I am new to flex. I am using flex builder 3 and rails for developing a application. I have tried all options but not able to configure flex builder 3 to use it with weborb(remote object). Does any one know how to do that? Please help me out.

Comments