Dima Berastau

What’s New in RestfulX 1.2.2

14 March 2009 – Vancouver, BC

Overview

Some of the key “themes” of this release are:

1. Distributed AIR apps (Better push/pull workflow, out of the box distributed configuration for Rails back-end, etc)

2. schema_to_yaml integration (helps with migrating existing Rails projects over to RestfulX)

3. More code generation options

And of course bug-fixing and a number of other cool tweaks that we’ll discuss further down.

Distributed AIR apps/Synchronization

For starters, you can generate distribution ready apps, pretty much out of the box. I am assuming that you’ve got a functional RestfulX development environment with restfulx and uuidtools gems installed.

Here’s a few steps to get you going:

$>rails foobar
$>cd foobar
$>vi config/environment.rb (add config.gem "restfulx" and config.gem "uuidtools")
$>./script/generate rx_config --distributed --air
$>create your db/model.yml file
$> rake db:refresh
$> rake rx:air:build
$> rake rx:air:run

Your Rails app will be configured to use UUIDs for all models and references, and the generated controllers will behave in a way that is more consistent with distributed RESTful behaviour (a-la CouchDB).

Examine the generated code to get a better idea of what it takes to set your Rails application with distribution/synchronization in mind.

To illustrate some of this stuff let’s have a look at a main AIR application file generated by default for distributed configuration.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:generated="foobar.components.generated.*"
  paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
  layout="horizontal" styleName="plain" initialize="init()">
  <mx:Script>
    <![CDATA[
      import air.net.SocketMonitor;
      import org.restfulx.events.PullEndEvent;
      import org.restfulx.events.PullStartEvent;
      import org.restfulx.events.PushEndEvent;
      import org.restfulx.events.PushStartEvent;
      import org.restfulx.controllers.ModelsController;
      import org.restfulx.controllers.ChangeController;
      import org.restfulx.services.ISyncingServiceProvider;
      import org.restfulx.services.http.XMLHTTPServiceProvider;
      import org.restfulx.services.air.AIRServiceProvider;
      import org.restfulx.Rx;
      import foobar.controllers.ApplicationController;
      
      [Bindable]
      private var socketMonitor:SocketMonitor;

      [Bindable]
      private var online:Boolean;
      
      [Bindable]
      private var syncStatus:String;

      private function init():void {
        Rx.httpRootUrl = "http://localhost:3000/";
        Rx.enableSync = true;

        socketMonitor = new SocketMonitor("localhost", 3000);
        socketMonitor.pollInterval = 2000; /* miliseconds */
        socketMonitor.addEventListener(StatusEvent.STATUS, onNetworkStatusChange);
        socketMonitor.start();
        
        ApplicationController.initialize([AIRServiceProvider], 
          AIRServiceProvider.ID, "foobar");
        
        Rx.changes.setSyncProviders(
          ISyncingServiceProvider(Rx.services.getServiceProvider(AIRServiceProvider.ID)),
          Rx.services.getServiceProvider(XMLHTTPServiceProvider.ID));

        Rx.changes.addEventListener(PushStartEvent.ID, onPushStart);
        Rx.changes.addEventListener(PushEndEvent.ID, onPushEnd);
        Rx.changes.addEventListener(PullStartEvent.ID, onPullStart);
        Rx.changes.addEventListener(PullEndEvent.ID, onPullEnd);
      }
      
      private function onPushStart(event:Event):void {
        syncStatus = "Pushing changes ...";
      }
      
      private function onPushEnd(event:Event):void {
        syncStatus = "Push complete.";
      }
      
      private function onPullStart(event:Event):void {
        syncStatus = "Pulling data...";
      }
      
      private function onPullEnd(event:Event):void {
        syncStatus = "Pull complete.";
      }
      
      private function onNetworkStatusChange(event:StatusEvent):void {
        online = (socketMonitor.available) ? true : false;
  
        if (online) {
          Rx.defaultServiceId = XMLHTTPServiceProvider.ID;
        } else {
          Rx.defaultServiceId = AIRServiceProvider.ID;
        }
      }

      private function getCurrentProviderName(id:int):String {
        switch (id) {
          case XMLHTTPServiceProvider.ID:
            return "Rails";
          case AIRServiceProvider.ID:
            return "AIR (SQLite)";
          default :
            return "No idea";
        }
      }
    ]]>
  </mx:Script>
  <mx:VBox height="100%">
    <mx:Label text="Current Provider: {getCurrentProviderName(Rx.defaultServiceId)}"/>
    <mx:Label text="Network Status: {online ? 'Online' : 'Offline' }"/>
    <mx:HBox>
      <mx:Button label="Push" click="{Rx.changes.push()}" enabled="{online}"/>
      <mx:Button label="Pull" click="{Rx.changes.pull()}" enabled="{online}"/>
    </mx:HBox>
    <mx:Label text="{syncStatus}"/>
  </mx:VBox>
  <mx:LinkBar dataProvider="{mainViewStack}" direction="vertical" 
    borderStyle="solid" backgroundColor="#EEEEEE"/>
  <mx:ViewStack id="mainViewStack" width="100%" height="100%">
    <!-- For a simple demo, put all the components here. -->
    <generated:LocationBox/>
    <generated:NoteBox/>
    <generated:ProjectBox/>
    <generated:TaskBox/>
    <generated:UserBox/>
  </mx:ViewStack>
</mx:WindowedApplication>

As you can see there’s quite a bit of stuff going on here.

We’ve got a socket monitor that watches port 3000 at localhost to see when we go online or offline and switches default RestfulX Service Provider from XMLHTTPServiceProvider to AIRServiceProvider and back appropriately.

The next thing that we do here is set up the synchronization controller telling it what is the source Service Provider
and what is the destination. In this case we want to sync from AIR to RubyOnRails by way of XMLHTTPServiceProvider.

Naturally, it would be handy to know when we start syncing and when we stop so that we can display some kind of a status update. To do this we hook up a few event listeners to Rx.changes controller.

Additionally, we’ve got a few buttons that allow to perform push and pull operations against a remote service provider.

You can use them in data-binding expressions or standalone.

Rx.changes.push(); // Pushes changes from a local service provider to remote
Rx.changes.pull(); // Pulls data from a remote service provider to local

If you don’t want to push/pull all the changes you can push and pull specific models only like so:

Rx.changes.push(Project, Task);
Rx.changes.pull(Project);

That’s pretty much all there’s to it.

More code-generation options

Many thanks to Robert Malko for implementing much of this!

You can now run and re-run rx_yaml_scaffold as many times as you like to refresh your generated code. No more conflicts with Rails helpers and migrations.

You can also specify specific models to generate or re-generate e.g. script/generate rx_yaml_scaffold model1 model2

More YAML configuration options for model.yml files

You can now use attachment_field as well as has_many_through keywords in your model.yml files. E.g.

project:
 - name: string
 - notes: text
 - start_date: date
 - end_date: date
 - completed: boolean
 - belongs_to: [user]
 - has_many: [tasks]

location:
 - name: string
 - notes: text
 - belongs_to: [user]
 - has_many: [tasks]

task:
 - name: string
 - notes: text
 - start_time: datetime
 - end_time: datetime
 - completed: boolean
 - next_action: boolean
 - belongs_to: [project, location, user]

note:
 - content: text
 - belongs_to: [user]

user:
 - login: string
 - first_name: string
 - last_name: string
 - email: string
 - has_many: [tasks, projects, locations]
 - has_one: [note]
 - attachment_field: [avatar]

This will get you a fully functional application with support for file uploads on the user model out of the box.

Miscellaneous improvements

Easier access to AuxAIRController via XRx class, which serves a similar purpose to Rx. E.g.

public function testFindAll():void {
  XRx.air(onFindAll).findAll(SimpleProperty, 
    ["name LIKE :name AND available = true", {":name": "%2%"}]);
}

This will find all projects with a name property that contains character 2 and available property set to true.

Eager loaded nested relationships.

public function testFindAllWithIncludes():void {
  XRx.air(onFindAllWithIncludes).findAll(Project, 
    ["name LIKE :name", {":name" : "%4%"}], ["tasks", "contractor"]);
}

This will find all projects with name that contains character 4 in the local SQLite database.
For the projects that were found their tasks and contractor relationships will be pre-fetched as well.

You can now send custom HTTP headers with Rx.http() and XMLHTTPServiceProvider, JSONHTTPServiceProvider. Just set Rx.customHTTPHeaders and off you go.

More natural HasMany + Sort annotations in your RestfulX Flex models. It goes something like this:

[HasMany(sort="name:descending")]
public var tasks:ModelsCollection;

And of course a bunch of bug fixes and other improvements.

Thanks a lot to everyone who submitted patches for this release. Your contributions are very much appreciated!