Dima Berastau

What’s New in RestfulX 1.2.3

17 July 2009 – Vancouver, BC

Overview

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

1. Server-Push (Integration with Juggernaut in order to support server-based push workflow)

2. Better Rails 2.3 support (accepts_nested_attributes_for support and recursive model serialization)

3. Ability to pass metadata from the server to the client, and not just client to the server

4. Bug fixing (see http://github.com/dima/restfulx_framework/issues/closed for details)

Server-Push with Juggernaut

Juggernaut (http://juggernaut.rubyforge.org/) is a Ruby on Rails plugin that allows you to push content to your Flash/Flex client directly from the server using an open XML socket.

Here’s a few steps to get you started with using Juggernaut and RestfulX together.

First, install Juggernaut as described here: http://juggernaut.rubyforge.org/

Then, let’s create a stab Rails+RestfulX application. I’m assuming you have the latest Rails 2.3.x installed.

$>sudo gem install restfulx
$>rails foobar
$>cd foobar
$>vi config/environment.rb (add config.gem "restfulx")
$>./script/generate rx_config

Next, Let’s check out the main application file generated for us called Foobar.mxml and change it to the following:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   xmlns:generated="foobar.views.generated.*"
   paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
   layout="horizontal" styleName="plain" initialize="init()">
   <mx:Script>
     <![CDATA[
       import org.restfulx.events.ServerPushEvent;
       import org.restfulx.controllers.ServerPushController;
       import org.restfulx.Rx;
       import foobar.controllers.ApplicationController;

       private var serverPushController:ServerPushController;

       private function init():void {
         Rx.enableLogging();
         ApplicationController.initialize();
         serverPushController = new ServerPushController("localhost", 5001);
         serverPushController.addEventListener(ServerPushEvent.ID, onServerPush);
       }

       private function onServerPush(event:ServerPushEvent):void {
         trace("message:" + event.message);
       }
     ]]>
   </mx:Script>
   <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. -->
   </mx:ViewStack>
</mx:Application>

This is a default bare-bones main application file generated by RestfulX with a few additions.

More specifically,
we create a new ServerPushController and set it up to connect to localhost on port 5001 (Juggernaut defaults). We then
add an event listener to receive notifications whenever a message gets pushed from the server. All we do in this example
is log that message to console, however it’s entirely possible to optionally unmarshall and cache the result (provided what
we sent from the server is a valid model representation).

Next, let’s compile the Flex app and start the juggernaut server with:

rake rx:flex:build
juggernaut -c config/juggernaut.yml

You can then navigate to http://localhost:3000 and try out pushing messages from the server or console. For example, do

./script/console
Juggernaut.send_to_all("hello from the server")

to test it out.

Rails 2.3.x and accepts_nested_attributes_for

Rails 2.3 introduced a cool new feature that allows you to recursively create model objects in one go. You can refer to http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes for more details on this.

It’s actually quite straight-forward to use this from your RestfulX-based Flex/AIR application. Here’s what you need to do.

Let’s create a simple Rails/RestfulX app. You can refer to http://wiki.github.com/dima/restfulx/restfulx-framework-and-rails-21 for details on how to get started with Rails and RestfulX.

Next, let’s modify the project.rb rails model file and change it to the following:

class Project < ActiveRecord::Base
  belongs_to :user
  has_many :tasks, :dependent => :destroy
  
  accepts_nested_attributes_for :tasks, :allow_destroy => true
end

Then, we can modify the generated ProjectBox.mxml view component and add a test function that goes something like this:

private function createProjectRecursively():void {
  var testProject:Project = new Project;
  testProject.name = "Test Recursive Project";
  testProject.notes = "Some Random Notes";
  testProject.startDate = new Date;
  testProject.endDate = new Date;
  
  testProject.tasks = new ModelsCollection;
  
  var testTask1:Task = new Task;
  testTask1.name = "Nested Task 1";
  
  testProject.tasks.addItem(testTask1);
  
  var testTask2:Task = new Task;
  testTask2.name = "Nested Task 2";
  
  testProject.tasks.addItem(testTask2);
  
  // recursively create Project and the tasks
  testProject.create({onSuccess: onRecursiveProjectCreate, recursive: true});
}

Note, the recursive parameter to create (also accepted by update and destroy).

Hopefully you are getting the drift by now. When you indicate that this model should be created recursively all non-null [HasOne] and [HasMany] relationships will be included by default.

[BelongsTo] relationships are still serialized using relationship id. If you’d like to include a particular [BelongsTo] relationship in recursive model creation mark it with [Nested] annotation.

That’s all there’s to it. Hook up a button or some other UI element to invoke this tester function and double-check that your project and tasks got saved by Rails correctly.

If you created a new Rails+RestfulX application from scratch using 1.2.3 release of the framework then the following middleware will be defined for you in config/initializers/restfulx.rb

# the following patches allow us to overwrite session key on file uploads from Flash, 
# which ends up creating a new session for every File.upload() invocation.

require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
    @session_token = "_session_id"
  end

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
      debugger
      env['HTTP_COOKIE'] = [ @session_key, params[@session_token] ].join('=').freeze 
        unless params[@session_token].nil?
    end
    @app.call(env)
  end
end

class FlexNestedAttributeMiddleware
  def initialize(app)
    @app = app
  end
  
  def call(env)
    req = Rack::Request.new(env)
    if req && req.path_info =~ /\.fxml$/
      if req.put? || req.post? || req.delete?
        req.params.each do |key,value|
          value.select { |k,v| k =~ /\_attributes$/ }.each do |match|
            env['rack.request.form_hash'][key][match[0]] = 
              ActiveSupport::JSON.decode(match[1])
          end
        end
      end
    end
    @app.call(env)
  end
end

ActionController::Dispatcher.middleware.insert_after 'ActionController::ParamsParser', 
  FlexNestedAttributeMiddleware

# If you have configured your Rails/Flex/AIR application to share authenticity_token
# comment this out to enable forgery protection. By default, this is disabled to allow
# generated code to work out of the box.
ActionController::Base.allow_forgery_protection = false

If you are upgrading from RestfulX 1.2.2 or earlier then you must replace the contents of your config/initializers/restfulx.rb with that or run ./script/generate rx_config again.

You can tweak the middleware to your liking if it doesn’t do exactly what you need.

Passing metadata from the server to the client.

to_fxml method now allows you to tag arbitrary metadata with your response to client request and later inspect that metadata. This is particularly handy for situations such as paging.

Here’s an example. Assume you have a controller called ProjectsController with an index action that looks like this:

def index
  @projects = Project.search(params[:search], params[:page])

  respond_to do |format|
    format.fxml  { render :fxml => @projects.to_fxml(:only => [:id, :name],
      :attributes => {:total_pages => @projects.total_pages}) }
  end
end

After you performed search and/or paging using this action. You can get access to any of the attributes you sent along with the response using something like this.

trace(Rx.models.cached(Project).metadata['total_pages'])

Bug-fixing

For a list of bugs fixed in this release refer to http://github.com/dima/restfulx_framework/issues/closed

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