Vixiom Axioms

August 26, 2006

Flash Remoting for Rails Tutorial 2 - Data Swings Both Ways

Filed under: ActionScript, Flash, Ruby on Rails Alastair @ 3:02 pm

In the first tutorial I showed how easy it is to pass data from Ruby on Rails to Flash via Flash Remoting, this tutorial will show that sending data objects back to Rails is just as simple. Backpack was one of the first apps to get people excited by Rails, I’ll be ripping off their To-do list feature and making it Flash rather than Ajax. I’m using Flash professional DataGrids for this, so unfortunately if you only have the regular flavor of Flash you’re out of luck (I’m guessing if you do webdev with Flash you use the pro version anyways). The entire source code for the tutorial can be downloaded here. The Flash source files are in the ‘fla’ directory.

As before start a new Rails app and call it ‘todoapp’

> rails todoapp

Change in the directory

> cd todoapp

Install WebORB for Ruy on Rails

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

There was briefly a bug with WebORB and ActionScript v2.0 components and sending parameters with your remoting call, however it’s been fixed (very quickly, thanks!). If you’ve previously installed WebORB in another Rails app you can update WebORB by adding –force at the end of the install command (warning: it will overwrite any config files you edited).

> ruby script/plugin install http://themidnightcoders.net:8089/svn/weborb –force

Create a MySQL database called ‘todoapp_development’ with a table called ‘todos’ (I’ve populated it with my wish list for Adobe) there’s a ‘todo.sql file’ in the ‘db’ direcotry of the source code.

CREATE TABLE `todos` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(50) NOT NULL default ,
  `done` tinyint(4) NOT NULL default ‘0′,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

insert into `todos` values(‘1′,‘Release Flex Builder for OS X’,‘0′),
 (‘2′,‘Release Creative Suite Universal’,‘0′),
 (‘3′,‘Release Apollo’,‘0′);

Create a ‘Todo’ model

> ruby script/generate model Todo

Create a remoting service inside of the app/services directory called ‘TodoService.rb’

require ‘weborb/context’
require ‘rbconfig’

class TodoService

  # getTodos for remoting
  def getTodos
    getTodoLists
  end

  # toggleTodo for for remoting
  def toggleTodo(id)
    todoToToggle = Todo.find(id)
    # set to true(1) if false(0) or vice versa (1 - 0 = 1, 1 - 1 = 0)
    todoToToggle.done = 1 - todoToToggle.done
    # if save send new data back to Flash
    if todoToToggle.save
      getTodoLists
    end
  end

  # toggleTodo for for remoting
  def addTodo(title)
    todoToAdd = Todo.new
    todoToAdd.title = title
    # if save send new data back to Flash
    if todoToAdd.save
      getTodoLists
    end
  end

  # get the todo lists (used throughout)
  def getTodoLists
    # putting lists in a hash as we have two sets of data
    todos = Hash.new
    todos[“not_done”] = Todo.find(:all, :conditions => [“done LIKE ?”, false])
    todos[“done”] = Todo.find(:all, :conditions => [“done LIKE ?”, true])
    return todos
  end

end

There’s a bit more going on here than the last tutorial which only had one method, this time there are three (four counting ‘getTodoLists’ which is used throughout the service). ‘getTodos’ calls ‘getTodoLists’ which fetches all the items in the ‘todos’ table. Since we want to split the todos into those that are done and not we make a Hash object to hold the filtered items. todos["not_done"] gets all that have their done column marked false and todos["done"] gets those that are true. You could return all the data and filter it in Flash but this way is easier.

‘toggleTodo(id)’ if a method that will be called from Flash via Remoting is has an argument ‘id’ that is used to find the todo item that you want to change from not done to done or vice versa. If the todo item has been succesfully toggled and saved we call ‘getTodoLists’ which returns the updated data back to Flash.

addTodo(title)‘ adds a new todo item with it’s ‘title’ coming in from Flash via Remoting, and returns the updated data again back to Flash using ‘getTodoLists‘.

With that setup and using the source for this tutorial you can now start the local lighttpd/WebBrick server and test the todo.fla in the Flash IDE. Click on the todo item’s title to send it to the other column.


Click on a todo…


…and it moves to the done list


The database is updated

 

Now for an overview of what’s happening on the Flash side. As in the first tutorial I’m using base Remoting.as and View.as classes that I extended with other classes. Below is the extened Remoting class ‘RemotingTodo.as’. New from the first tutorial are params/args (args? I never know the difference I started out as a designer) being sent to Rails eventObj.data.id’ in the line
‘var pc:PendingCall = this.svc.toggleTodo(eventObj.data.id);’
of ‘toggleTodo‘ where eventObj.data is a Todo item object staright from Rails, so appending ‘.id’ works just as it would in Rails (I cover event dispatching in the first tutorial if this is as confusing as it reads, Flex has event dispatching built in which makes things easier)

/**
*   @class RemotingTodo
*   @extends Remoting
*   @author Alastair Dawson
*   @copyright 2006 Vixiom Communications, LLC
*/

import mx.remoting.*;
import mx.rpc.*;

class com.vixiom.remoting.RemotingTodo extends com.vixiom.remoting.Remoting
{
    // todos holder array
    private var todos:Object;

    //////////////////////////////////////////////////////////////////////
    //
    // Constructor (gatewayURL, servicePath, userid, password)
    //
    //////////////////////////////////////////////////////////////////////

    public function RemotingTodo (gURL, sp, u, p)
    {
        super(gURL, sp);
        // this.svc.connection.setCredentials(u, p);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Get todos (handler:onGetTodos)
    //
    //////////////////////////////////////////////////////////////////////

    public function getTodos()
    {
        trace(“// remoting - getting todos”)
        // create a pending call out to rails
        var pc:PendingCall = this.svc.getTodos();
        // create a responder to handle the return from rails
        pc.responder = new RelayResponder(this, “onGetTodos”, “handleRemotingError”);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Toggle todo (handler:onGetTodos)
    //
    //////////////////////////////////////////////////////////////////////

    public function toggleTodo(eventObj:Object)
    {
        trace(“// remoting - toggle todo: “ + eventObj.data.title)
        // create a pending call out to rails
        var pc:PendingCall = this.svc.toggleTodo(eventObj.data.id);
        // create a responder to handle the return from rails
        pc.responder = new RelayResponder(this, “onGetTodos”, “handleRemotingError”);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Add todo (handler:onGetTodos)
    //
    //////////////////////////////////////////////////////////////////////

    public function addTodo(eventObj:Object)
    {
        trace(“// remoting - add todo: “ + eventObj.data)
        // create a pending call out to rails
        var pc:PendingCall = this.svc.addTodo(eventObj.data);
        // create a responder to handle the return from rails
        pc.responder = new RelayResponder(this, “onGetTodos”, “handleRemotingError”);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // onGetTodos handler
    //
    //////////////////////////////////////////////////////////////////////

    public function onGetTodos (re:ResultEvent)
    {
        if (re != undefined)
        {
            trace(“// onGetTodos broadcaster - Word!”)

            // put result in recordset
            todos = re.result;

            // dispatch event to the view
            dispatch(todos, “onGetTodos”);
        }
    }
}

In the todo.fla file we create instances of the RemotingTodo and ViewTodo classes and connect broadcasters to their listeners. The previous tutorial only had one event being broadcast between the classes (Remoting telling the View when it had fetched the data) this time there are two broadcasters for the View to tell Remoting to "toggleTodo" and "addTodo". The todo lists are Flash DataGrid components and they get event listeners that broadcast when they ‘change’ i.e. someone clicks on a row.

/**
*   @author Alastair Dawson
*   @copyright 2006 Vixiom Communications, LLC
*/

// import remoting, view, and debug
import mx.remoting.debug.NetDebug;
import mx.utils.Delegate;
import com.vixiom.remoting.RemotingTodo;
import com.vixiom.view.ViewTodo;

// ini debug
NetDebug.initialize ();

// style for grids (apple green is fugly)
_global.style.setStyle (“themeColor”, “haloOrange”);

iniApp();

// setup and start
function iniApp()
{
    // create remoting & view objects
    var RTodo:RemotingTodo  = new RemotingTodo (“http://localhost:3000/weborb”, “TodoService”); // weborb gateway, ruby class name
    var VTodo:ViewTodo      = new ViewTodo (_root);

    // set up listeners
    RTodo.addEventListener (“onGetTodos”, Delegate.create (VTodo, VTodo.onGetTodos));
    VTodo.addEventListener (“toggleTodo”, Delegate.create (RTodo, RTodo.toggleTodo));
    VTodo.addEventListener (“addTodo”, Delegate.create (RTodo, RTodo.addTodo));

    todo_dg.addEventListener(“change”, Delegate.create (VTodo, VTodo.onTodoChange));
    done_dg.addEventListener(“change”, Delegate.create (VTodo, VTodo.onDoneChange));

    add_btn.onRelease = Delegate.create(VTodo, VTodo.addTodo);

    // start the app, get the todos
    RTodo.getTodos();
}

Below is the extended View class, it’s all ActionScript related so I won’t go into it but there’s a ‘todos’ object to hold our data from Rails (after it’s passed from the Remoting class) there’s validation in there to make sure the add to do input field isn’t blank (again Flex has validation built in).

/**
*   @class ViewTodo
*   @extends View
*   @author Alastair Dawson
*   @copyright 2006 Vixiom Communications, LLC
*/
import mx.controls.DataGrid
import mx.controls.TextInput
import mx.controls.Button

class com.vixiom.view.ViewTodo extends com.vixiom.view.View
{
    // todos holder object
    private var todos:Object;

    // objects on stage
    private var todo_dg     :DataGrid;
    private var done_dg     :DataGrid;
    private var title_txt   :TextInput;

    //////////////////////////////////////////////////////////////////////
    //
    // Constructor (target)
    //
    //////////////////////////////////////////////////////////////////////

    public function ViewTodo (t:MovieClip)
    {
        super(t);

        // references for objects on the stage
        todo_dg     = target.todo_dg;
        done_dg     = target.done_dg;
        title_txt   = target.title_txt;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // onGetTodos listener
    //
    //////////////////////////////////////////////////////////////////////

    public function onGetTodos(eventObj:Object)
    {
        trace (“// onGetTodos listener - Word heard!”);

        // todos object
        todos = eventObj.data;

        // update lists
        updateLists();
    }

    //////////////////////////////////////////////////////////////////////
    //
    // todo & done dataGrid listeners
    //
    //////////////////////////////////////////////////////////////////////

    public function onTodoChange(eventObj:Object)
    {
        // set the selected todo
        var selectedTodo = todos.not_done[eventObj.target.selectedIndex];

        trace(“// view - on change: “ + selectedTodo.title);

        // tell remoting to change select item to done
        dispatch(selectedTodo, “toggleTodo”);
    }

    public function onDoneChange(eventObj:Object)
    {
        // set the selected todo
        var selectedTodo = todos.done[eventObj.target.selectedIndex];

        trace(“// view - on change: “ + selectedTodo.title);

        // tell remoting to move the select item to todo
        dispatch(selectedTodo, “toggleTodo”);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // update the todo and done lists
    //
    //////////////////////////////////////////////////////////////////////

    private function updateLists()
    {
        // populate data grids with remoting objects
        todo_dg.dataProvider = todos.not_done;
        done_dg.dataProvider = todos.done;

        /* when using dataProviders with dataGrids they show all your columns
            in the database, remove the columns we don’t want to see */
        todo_dg.removeColumnAt(todo_dg.getColumnIndex(“id”));
        todo_dg.removeColumnAt(todo_dg.getColumnIndex(“done”));
        done_dg.removeColumnAt(done_dg.getColumnIndex(“id”));
        done_dg.removeColumnAt(done_dg.getColumnIndex(“done”));
    }

    //////////////////////////////////////////////////////////////////////
    //
    // add button
    //
    //////////////////////////////////////////////////////////////////////

    public function addTodo()
    {
        if (notEmpty(title_txt.text)) {
            // not empty dispatch to remoting (RTodo)
            dispatch(title_txt.text, “addTodo”);
            // reset style (might have been an error previously)
            title_txt.setStyle (“backgroundColor”, 0xFFFFFF);
            title_txt.setStyle (“themeColor”, “haloOrange”);
            title_txt.setStyle (“color”, 0×000000);
        } else {
            title_txt.text = “Todo can’t be blank”;
            // style the text box to alert the user to the error
            title_txt.setStyle (“backgroundColor”, 0xFFDFDC);
            title_txt.setStyle (“themeColor”, 0xCC0000);
            title_txt.setStyle (“color”, 0×990000);
        }
    }

    //////////////////////////////////////////////////////////////////////
    //
    // validate field
    //
    //////////////////////////////////////////////////////////////////////

    private function notEmpty (s:String):Boolean
    {
        if (s == “” || s == “Todo can’t be blank”) {
            return false
        } else {
            return true
        }
    }

}

That’s it! you now know how to pass data back and forth between Rails and Flash with Flash Remoting, now go build some cool shise!

PS For ActionScripters looking to learn Rails Agile Web Development is a great book to get started with, there’s also the ‘pickaxe‘ book online for learning Ruby. For Railers wnating to know more about ActionScript Essential ActionScript 2.0 is the best programming, never mind ActionScript, book I’ve ever read.

Digg! submit Flash Remoting for Rails Tutorial 2 - Data Swings Both Ways to stumbleupon.com submit Flash Remoting for Rails Tutorial 2 - Data Swings Both Ways to del.icio.us submit Flash Remoting for Rails Tutorial 2 - Data Swings Both Ways to reddit.com Like this post? subscribe to the feed.

17 Comments »

  1. Flash Remoting for Rails Tutorial 2 - Data Swings Both Ways…

    In the first tutorial I showed how easy it is to pass data from Ruby on Rails to Flash via Flash Remoting, this tutorial will show that sending data objects back to Rails is just as simple….

    Trackback by Anonymous — August 27, 2006 @ 9:31 am

  2. Hey thanks for this tutorial. I have a question regarding the object sent back from weborb when you request the todolist. It works without hassle when I set the done/notDone arrays as DataGrid dataProvider. But if I trace the objects all I get is a bounch of “undefined”. I can’t seem to access the objects until I use them as a dataProvider for a List component. What’s up with this?

    I tried building some of my own services but the only data I can receive and trace are simple and native objects. Arrays and hashes makes everything go “undefined” allthough apparently all data is there because the List components can read them somehow. Guidance on this would be much appreciated.

    Comment by David — September 4, 2006 @ 1:32 am

  3. Hey David,

    This messed me up for a while as well, if you trace a Hash or an Array of objects you an undefined for the length of the Array (if Array.length is 3 then undefined, undefined, undefined). I don’t know if it’s a bug in WebORB or what, I suspect that if you’re used to Remoting using ColdFusion or AMFPHP (I was a big AMFPHP user before Rails) you were getting recordSets returned from your remoting calls so you could trace the recordset and see what was in there.

    WebORB returns objects directly, as a string or a number, as one object, or as in this tutorial a set of objects in a Hash. Again it might be a bug in WebORB but ideally if you trace the Hash (again with a length of three) you should see [object], [object], [object] not undefined, undefined, undefined.

    If you want to work with the data directly in this tutorial (not using a DataGrid) you can access things like this…

    In the RemotingTodo.as file add a trace after ‘todos = re.result;’ (line 87) using dot notation to run down to the todo’s title

    trace(todos.not_done[0].title);

    ‘0′ being the first object in the ‘todos’ Hash from Rails. To get the second

    trace(todos.not_done[1].title);

    obviously if you were working with a lot of data you would loop over the Hash object

    for (var i = 0; i < todos.length; i++)
    {
    trace(todos.not_done[i].title);
    }

    Hope that answers your question, WebORB for Rails is still in beta hopefully things will work as expected when the final version is released.

    Comment by KreeK — September 4, 2006 @ 10:03 am

  4. Thanks for the reply.

    I’m going to post this question on the Rails WebORB discussion forum. They should know if it’s a bug or not :)

    Comment by David — September 12, 2006 @ 8:15 am

  5. VERY GOOD points

    Comment by EQ2 GOLD — January 3, 2007 @ 12:59 pm

  6. Hi,
    i just tried to use the example and doesn’t works to me.
    I got a NoMethodError in WeborbController#index
    and exactly, request.raw_post.each_byte {|byte| input.push byte } is the problem
    because request.raw_post is nil. So, i don’t know what did i wrong. But, if someone knows, please
    let me know.
    Thanks for all and of course to people who write this tutorial.

    Comment by Minä — January 9, 2007 @ 4:09 am

  7. Hi ,

    yeah same here your tutorial is pretty sweet but i can’t get it running here
    i think everything is running pretty fine here when i install the weborb and refer to the weborb testfiles
    the weborbtests are running and i can see the result Objects but since i am noob to this rails stuff i have maybee problems with the scaffolding part

    i followed a tutorial from http://www.flexonrails.net/ and used Radrails and it worked pretty fine anyhow i cannot get this one running

    any help would be greatly appreciated

    Comment by m@ — February 25, 2007 @ 4:09 pm

  8. Hi ,

    i am havin a Error too . In the Flash Ide i get the trace
    “// remoting - getting todos” but nothing appears and the webrick is running ?.
    Since i am using amfPhp Remoting alot i still stick to the mx Remoting Classes . But i already installed the new Remoting Components .
    Does anybody knows why ??

    please give me a hint

    thanks for the Tutorial and the help upfront

    ( btw : why did you deleted my post before ? )

    Comment by Matthias — February 25, 2007 @ 5:51 pm

  9. Ok ,

    thnx alot for the very nice remoting tutorial i found out the solution myself :

    in the case that you dont want to use the sourcefiles supplied @ the beginning of the tutorial and you copy and paste the Code out of the sourceview you need to replace the apostrophies in the .rb files maybee they are bitwise wrong from the css view :P

    thnx alot m@

    Comment by Matthias — February 26, 2007 @ 10:54 am

  10. Matthias,

    Yes I should change the font the code tags use, I’ve even had that problem when copying stuff from my own tutorials!

    thanks,
    Alastair

    Comment by KreeK — February 26, 2007 @ 10:59 am

  11. if theres a way to call rails services with actionscript 3 using NetConnect classes, please tell! I’m not sure what adobe’s plans are for maintaining the remoting classes, but apparently they’ve included some similar functionality.

    Comment by rich — March 6, 2007 @ 4:31 pm

  12. Hi there,

    Thanks for this tutorial - I learned a great deal, and have things working great locally. I’m having the same problem as Mina, however - “I got a NoMethodError in WeborbController#index
    and exactly, request.raw_post.each_byte {|byte| input.push byte } is the problem”, but only when I upload the interface through my hosting service (Dreamhost). could this be a server-side issue? I don’t know if there are server-side components in the Flash Remoting Components…

    anyone else encounter this or try it on a 3rd party hosting service?

    Thanks!

    Comment by Molly — August 3, 2007 @ 10:49 am

  13. Hi Molly,

    The error you’re getting is on the server side, all the Flash stuff ends up packaged in the SWF file. If it works locally then something on the remote server is different than your local version, this could be one of many things; a different version of Rails, a different ruby gem on the server (or a missing gem).

    When I get problems like this I try to get weborb working on the server in a fresh Rails app just to make sure it’s not my code messing stuff up :)

    Follow this example http://themidnightcoders.com/weborb/rubyonrails/gettingstarted.htm up until they say to check http://localhost:3000/examples/main.html , that’s the weborb test suite, if it runs in a clean rails app you know weborb can work on your server and the problem is in your code.

    Hope that helps, good luck!

    Comment by Alastair — August 3, 2007 @ 1:19 pm

  14. Hmm… thanks for the response, Alastair. I checked out WebOrb in a fresh app, and still had problems (send failed in all examples). Is there any reason why the server would interfere with the way information is sent between flash and rails? Particularly, why request.raw_post would come back as nil into rails?

    Has anyone had any experience with fixing something like this? There are people all over the midnightcoders forum with similar issues, and not many viable solutions. If anyone has insight, you might have an army of people indebted to you… :)

    Molly

    Comment by Molly — August 6, 2007 @ 3:28 pm

  15. Hi again ,

    i extendet the example so that you have full crud functionallity .
    Its pretty funny if you see how easy it is to delete a Todo aswell :P

    require ‘weborb/context’
    require ‘rbconfig’

    class TodoService

    # getTodos for remoting
    def getTodos
    getTodoLists
    end

    def getDeleteTodos
    return todos
    getTodoLists
    end
    # toggleTodo for for remoting
    def toggleTodo(id)
    todoToToggle = Todo.find(id)
    # set to true(1) if false(0) or vice versa (1 - 0 = 1, 1 - 1 = 0)
    todoToToggle.done = 1 - todoToToggle.done
    # if save send new data back to Flash
    if todoToToggle.save
    getTodoLists
    end
    end

    def deleteTodo(id)
    Todo.delete(id)
    todoToDelete = Todo.find(id)
    if todoToDelete.delete
    getTodos
    end
    end
    # toggleTodo for for remoting
    def addTodo(title)
    todoToAdd = Todo.new
    todoToAdd.title = title
    # if save send new data back to Flash
    if todoToAdd.save
    getTodoLists
    end
    end
    # get the todo lists (used throughout)
    def getTodoLists
    # putting lists in a hash as we have two sets of data
    todos = Hash.new
    todos[”not_done”] = Todo.find(:all, :conditions => [”done LIKE ?”, false])
    todos[”done”] = Todo.find(:all, :conditions => [”done LIKE ?”, true])
    return todos
    end

    end

    Comment by Matthias — October 10, 2007 @ 1:28 am

  16. you can download my update here :

    http://www.mat3d.com/Tutorials/todoapp.zip

    Comment by Matthias — October 10, 2007 @ 4:28 am

  17. […] http://blog.vixiom.com/2006/08/26/flash-remoting-for-rails-tutorial-data-swings-both-ways/ […]

    Pingback by MaxUp Blog’s | » Links sobre Flex + Ruby On Rails — December 28, 2007 @ 8:35 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-Spam Image

Powered by WordPress