Using RubyAmf for creating a CRUD application in Rails

CRUD applications can be easily created using Ruby on Rails as the backend and Flex as the frontend app using the XML format as demonstrated here.

Another way to create such applications is using the AMF protocol which is optimized for network communications and stores objects in binary format so consumes less bandwidth. Another compelling reason for using this library is that it doesn’t feel like a hack as it integrates very nicely with Rails :)

The Ruby port of this protocol known as RubyAmf can be downloaded from from here. More information about this protocol can be found here.

Lets start making the application.

 

Create a Rails Application:

   1: > rails amf_demo

This will create a rails application. Please note that I am using Rails 2.1.2 for this example and it is not tested on any other Rails version.

Generate a scaffold:

   1: > ruby script/generate scaffold blog_post title:string body:text

Create Tables and run Migrations:

   1: > rake db:migrate

   1: > rake db:create

Install the RubyAmf Plugin:

 

 

Step 1: Lets try if everything is working fine by creating a new Flex Project

Open Flex Builder and create a new Flex Project

image

 

Select all the default options and click Finish.

Now right click on the project you just created in Flex Navigator and select Properties. The Flex Build Path should have the following settings:

image

The Output folder will point to the public folder of the amf_demo Rails application and the Output folder URL will point to the Rails server which in  my case is localhost on port 80.

 

Start the Rails server:

   1: > ruby script/server -p 80

And start the project in Debugging mode. The Web Browser will show up a blank page.

image

 

Some helper files can be downloaded from the RubyAmf project site here

These are located in the “flash9_helloworld\flash9_helloworld\app\flash\org” of the archive.

Or just download the extracted archive from here. Put these files in the “src” directory of your Flex project. So the “src” folder should now have a folder with name “org” inside it.

Fetching Data off the server:

Firstly we will need to update the “BlogPostsController” so that it can generate amf content. It can be done by modifying the the index action to following:

   1: def index
   2:   @blog_posts = BlogPost.find(:all)
   3:  
   4:   respond_to do |format|
   5:     format.html # index.html.erb
   6:     format.amf  { render :amf => @blog_posts }
   7:     format.xml  { render :xml => @blog_posts }
   8:   end
   9: end

 

Now change the main mxml file of the Flex project to include code that fetches data off the server. In the current project the name of the file should be “amf_demo.mxml”

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
   3:     creationComplete="creationCompleteEvent(event)" xmlns:ssr="org.rubyamf.remoting.ssr.*">
   4:     <mx:Script>
   5:         <![CDATA[
   6:             import org.rubyamf.remoting.ssr.FaultEvent;
   7:             import org.rubyamf.remoting.ssr.ResultEvent;
   8:             import org.rubyamf.remoting.ssr.RemotingService;
   9:             private function creationCompleteEvent(evemt:Event):void{
  10:                 var service:RemotingService = new RemotingService("/rubyamf/gateway", "BlogPostsController");
  11:                 service.index([], onIndexResult, onFault);
  12:             }
  13:             private function onIndexResult(event:ResultEvent):void{
  14:                 trace(event.result);
  15:             }
  16:             private function onFault(event:FaultEvent):void{
  17:                 trace(event.fault);
  18:             }
  19:         ]]>
  20:     </mx:Script>
  21: </mx:Application>

 

The main component of this code is the “RemoteService” class and in the above given example it is invoking the index action on the server. On running this example the onIndexResult should be triggered and the debug window should show all the BlogPosts in the db. You can also put  a break-pointer to analyze the results.

image

One thing you will notice here is that the output result are plain ActionScript Objects. In the next step I will show how to map these objects to an ActionScript class.

Download code for upto this point:

  1. Rails Code
  2. Flex Code

 

Step 2: Mapping results to Action Script classes

Generate Mappings:

In the project root directory run the following command.

   1: > ruby script/generate rubyamf_mappings

Copy the output of this command to config/rubyamf_config.rb in the Rails project and now this file should look like this after some modifications:

   1: require 'app/configuration'
   2: module RubyAMF
   3:   module Configuration
   4:     #set the service path used in all requests
   5:     # RubyAMF::App::RequestStore.service_path = File.expand_path(RAILS_ROOT) + '/app/controllers'
   6:  
   7:     # => CLASS MAPPING CONFIGURATION
   8:     
   9:     # => Global Property Ignoring
  10:     # By putting attribute names into this array, you opt in to globally ignore these properties on incoming objects.
  11:     # If you want to ignore specific properties on certain objects, use the :ignore_fields property in a 
  12:     # Class Mapping definition (see CLASS MAPPING DEFINITIONS)
  13:     # ClassMappings.ignore_fields = ['created_at','created_on','updated_at','updated_on']
  14:   
  15:     # => Case Translations
  16:     # Most actionscript uses camelCase instead of snake_case. Set ClassMappings.translate_case to true if want translations to occur.
  17:     # The translations only occur on object properties
  18:     # An incoming property like: myProperty gets turned into my_property
  19:     # An outgoing property like my_property gets turned into myProperty
  20:     ClassMappings.translate_case = true
  21:   
  22:     # => Force Active Record Ids
  23:     # includes the id field for activerecord objects even if you don't specify it when using custom attributes. This is important for deserialization
  24:     # where ids are needed to keep active record association integrity.
  25:     # ClassMappings.force_active_record_ids = true
  26:     
  27:     # => Hash key access
  28:     # You can choose how keys in hashes are created. As :string, :symbol, or :indifferent. 
  29:     # :string creates keys as hash['key']
  30:     # :symbol creates keys as hash[:key]
  31:     # :indifferent uses rails HashWithIndifferentAccess so you can use hash[:key] of hash['key']
  32:     # There are performance issues with HashWithIndifferentAccess. Use :symbol or :string for best performance.
  33:     # The default is :symbol
  34:     ClassMappings.hash_key_access = :string
  35:     
  36:     # => Assume Class Types
  37:     # This tells RubyAMF to assume class type transfers. So when you register a class Alias from Flash or Flex like this:
  38:     # Flash::   fl.net.registerClassAlias('User',User)
  39:     # Flex::    [RemoteClass(alias='User')]
  40:     # RubyAMF will automagically convert it to a User active record without you having to create a class mapping.
  41:     # This also works with non active record class mappings. See the wiki on the google code page for a downloadable example.
  42:     # ClassMappings.assume_types = false
  43:     
  44:     # => Class Mapping Definitions
  45:     # A Class Mapping definition conists of at least these two properties:
  46:     # :actionscript   # The incoming action script class to watch for
  47:     # :ruby           # The ruby class to turn it into
  48:     #
  49:     # => Optional value object properties:
  50:     # :type           # Used to spectify the type of VO, valid options are 'active_record', 'custom',  (or don't specify at all)
  51:     # :associations   # Specify which associations to read on the active record (only applies to active records)
  52:     # :attributes     # Specifically which attributes to include in the serialization
  53:     # :methods        # An array of methods to call and place values in a similarly named attribute on the Actionscript Object (outgoing, or Rails => Actionscript only)
  54:     # :ignore_fields  # An array of field names you want to ignore on incoming classes
  55:     #
  56:     # If you are using ActiveRecord VO's you do not need to specify a fully qualified class path to the model, you can just define the class name, 
  57:     # EX: ClassMappings.register(:actionscript => 'vo.Person', :ruby => 'Person', :type => 'active_record')
  58:     #
  59:     # If you are using custom VO's you would need to specify the fully qualified class path to the file
  60:     # EX: ClassMappings.register(:actionscript => 'vo.Person', :ruby => 'org.mypackage.Person')
  61:     #
  62:     # ClassMappings.register(:actionscript => 'Person', :ruby => 'Person', :type => 'active_record', :attributes => ["id", "address_id"])
  63:     # ClassMappings.register(:actionscript => 'User', :ruby => 'User', :type => 'active_record', :associations=> ["addresses", "credit_cards"])
  64:     # ClassMappings.register(:actionscript => 'Address', :ruby => 'Address', :type => 'active_record')
  65:     # ClassMappings.register(:actionscript => 'User', :ruby => 'User', :type => 'active_record', :associations=> ["addresses", "credit_cards"], :methods => ["friends"])
  66:     #
  67:     # => Class Mapping Scope (Advanced Usage)
  68:     # You can also specify a class mapping scope if you want. For example, lets say you need certain attributes for a book when you are viewing a book
  69:     # in flex as opposed to editing a book (where you would need more parameters). You can define a scope mapping parameter for ":attributes"
  70:     # or for ":associations." You're mapping would look something like this.
  71:     # ClassMappings.register(
  72:     #   :actionscript  => 'com.mixbook.vo.books.BookVO',      
  73:     #   :ruby          => 'Book',      
  74:     #   :type          => 'active_record',
  75:     #   :associations  => ["access_info", "pages", "page_ratio"],  
  76:     #   :attributes    => {:viewing => ["description", "title"], :editing => ["id","published_at","theme_id"] }    <=== notice the hash instead of an array
  77:     #
  78:     # Now, to call the class mapping scope of editing (you are sending objects to the editing application), your controller call would look like this:
  79:     # EX: render :amf => book, :class_mapping_scope => :editing
  80:     #
  81:     # You can also specify a default scope to use. If you don't set this and you don't specify a class mapping scope on an attribute or association, then 
  82:     # it will not have a scope to use and will not add any attributes or associations (whichever it cant match) to that association.
  83:     # ClassMappings.default_mapping_scope = :viewing
  84:   
  85:     # => Date Conversion
  86:     # Incoming dates from Flash by default are Time objects, this can conver to DateTime if needed
  87:     # ClassMappings.use_ruby_date_time = false
  88:   
  89:     # => Use Array Collection
  90:     # By setting this to true, you opt in to using array collections for all the arrays generated by the body of your request.
  91:     # Note: This only works for amf3 with Remote Object, NOT with Net Connection.
  92:     # ClassMappings.use_array_collection = false
  93:   
  94:     # => Check for Associations
  95:     # Enabling this will automagically pick up eager loaded association data on objects returned through RubyAMF.
  96:     # If this is disabled, you will need to specify any associations you DO want picked up in the ClassMapping
  97:     # ClassMappings.check_for_associations = true
  98:     
  99:     # => NAMED PARAMETER MAPPING CONFIGURATION
 100:     
 101:     #=> Always Put Remoting Parameters into the "params" hash
 102:     # If set to true, arguments from Flash/Flex will come in to the controllers as params[0], params[1], etc.. This is especally useful if you are sending huge objects
 103:     # from Flex into Ruby so it doesnt eat up all your output window with outputting the params in the controller/action header information while in dev mode.
 104:     # Even if its set to false, if you specify specific ParameterMappings, those will still get entered as the param keys you specify. Likewise, you
 105:     # always have access to the parameters from rubyamf in your controller by calling rubyamf_params[0], rubyamf_params[1], etc regardless of
 106:     # if it this is set or not.
 107:     # ParameterMappings.always_add_to_params = true
 108:      
 109:     # => Return Top Level Hash
 110:     # For those scaffolding users out there, who want the top-level object to come as a hash so scaffolding works out of the box.
 111:     ParameterMappings.scaffolding = true
 112:   
 113:     # => Incoming Remoting Parameter Mappings
 114:     # Incoming Remoting Parameter mappings allow you to map an incoming requests parameters into rails' params hash
 115:     #
 116:     # Here's an example:
 117:     # ParameterMappings.register(:controller => :UserController, :action => :find_friend, :params => { :friend => "[0]['friend']" })
 118:     ClassMappings.register(
 119:       :actionscript  => 'BlogPost',
 120:       :ruby          => 'BlogPost',
 121:       :type          => 'active_record',
 122:       :attributes    => ["id", "title", "body", "created_at", "updated_at"])
 123:   end
 124: end

 

Create a new ActionScript Class with name “BlogPost” and it should have the following code:

   1: package
   2: {
   3:     [RemoteClass(alias='BlogPost')]
   4:     public class BlogPost
   5:     {
   6:         public var id:int;
   7:         public var title:String; 
   8:         public var body:String;
   9:     }
  10: }

 

And modify the “amf_demo.mxml” file to include this file. Also change the “service.index” method to to use this file:

   1: service.index([], onIndexResult, onFault, BlogPost);

 

Now when you debug and check the output in the “onIndexResult” it should look like:

image

 

Download code for upto this point:

  1. Rails Code
  2. Flex Code

 

Step 3: Creating a UI

Change the amf_demo.mxml to the following code.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
   3:     creationComplete="creationCompleteEvent(event)" xmlns:ssr="org.rubyamf.remoting.ssr.*">
   4:     <mx:Script>
   5:         <![CDATA[
   6:             import mx.collections.ArrayCollection;
   7:             import org.rubyamf.remoting.ssr.FaultEvent;
   8:             import org.rubyamf.remoting.ssr.ResultEvent;
   9:             import org.rubyamf.remoting.ssr.RemotingService;
  10:             [Bindable]
  11:             private var blogPosts:ArrayCollection;
  12:             
  13:             private function creationCompleteEvent(evemt:Event):void{
  14:                 var service:RemotingService = new RemotingService("/rubyamf/gateway", "BlogPostsController");
  15:                 service.index([], onIndexResult, onFault, BlogPost);
  16:             }
  17:             private function onIndexResult(event:ResultEvent):void{
  18:                 blogPosts = new ArrayCollection(event.result as Array);
  19:             }
  20:             private function onFault(event:FaultEvent):void{
  21:                 trace(event.fault);
  22:             }
  23:         ]]>
  24:     </mx:Script>
  25:     
  26:     <mx:List width="300" dataProvider="{blogPosts}" labelField="title" />
  27: </mx:Application>

Here the result of the onIndexResult event is bound to a list to Display all the blog posts in the database. This is probably the simplest way to do this. The output will look like:

image

Download code for upto this point:

  1. Rails Code (no changes)
  2. Flex Code

 

Step 4: Refactoring Completing CRUD

As a single page can display more than one list, it would make more sense if we move the code into separate files for each list. So I will be moving all the BlogPost related code in the “BlogPost class.

So the “BlogPost.as” will look like:

   1: package
   2: {
   3:     import mx.collections.ArrayCollection;
   4:     import org.rubyamf.remoting.ssr.FaultEvent;
   5:     import org.rubyamf.remoting.ssr.ResultEvent;
   6:     import org.rubyamf.remoting.ssr.RemotingService;
   7:             
   8:     [RemoteClass(alias='BlogPost')]
   9:     public class BlogPost
  10:     {
  11:         public var id:int;
  12:         public var title:String; 
  13:         public var body:String;
  14:         
  15:         [Bindable]
  16:         public static var blogPosts:ArrayCollection;
  17:         private var service:RemotingService = new RemotingService("/rubyamf/gateway", "BlogPostsController");
  18:         
  19:         public static function getBlogPosts():void{
  20:             (new BlogPost()).getBlogPosts();
  21:         }
  22:         private function getBlogPosts():void{
  23:             service.index([], onIndexResult, onFault, BlogPost);
  24:         }
  25:         private function onIndexResult(event:ResultEvent):void{
  26:             BlogPost.blogPosts = new ArrayCollection(event.result as Array);
  27:         }
  28:         private function onFault(event:FaultEvent):void{
  29:             trace(event.fault);
  30:         }
  31:     }
  32: }

 

And “amf_demo.mxml”:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
   3:     creationComplete="creationCompleteEvent(event)" xmlns:ssr="org.rubyamf.remoting.ssr.*">
   4:     <mx:Script>
   5:         <![CDATA[
   6:             private function creationCompleteEvent(evemt:Event):void{
   7:                 BlogPost.getBlogPosts();
   8:             }
   9:         ]]>
  10:     </mx:Script>
  11:     <mx:List width="300" dataProvider="{BlogPost.blogPosts}" labelField="title" />
  12: </mx:Application>

 

Download code for upto this point:

  1. Rails Code (no changes)
  2. Flex Code

 

Step 5: Completing CRUD

After further refactoring the code and adding methods for creating/editing/destroying the code looks like:

  • amf_demo.mxml
   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
   3:     creationComplete="creationCompleteEvent(event)" xmlns:ssr="org.rubyamf.remoting.ssr.*">
   4:     <mx:Script>
   5:         <![CDATA[
   6:             private function creationCompleteEvent(event:Event):void{
   7:                 BlogPost.parentContainer = this;
   8:                 BlogPost.getBlogPosts();
   9:             }
  10:             private function blogPostCreateEvent(event:Event):void{
  11:                 (new BlogPost(title.text, body.text)).create();
  12:             }
  13:             private function blogPostUpdateEvent(event:Event):void{
  14:                 var blogPost:BlogPost     = currentPost();
  15:                 blogPost.title             = title.text;
  16:                 blogPost.body              = body.text; 
  17:                 blogPost.update();
  18:             }
  19:             private function blogPostdestroyEvent(event:Event):void{
  20:                 currentPost().destroy();
  21:             }
  22:             private function currentPost():BlogPost{
  23:                 return blogPosts.selectedItem as BlogPost;
  24:             }
  25:         ]]>
  26:     </mx:Script>
  27:     <mx:List width="300" dataProvider="{BlogPost.blogPosts}" labelField="title" id="blogPosts"/>
  28:     <mx:Panel layout="vertical" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
  29:         <mx:HBox width="100%">
  30:             <mx:Label text="Post Title" width="60"/>
  31:             <mx:TextInput id="title" text="{blogPosts.selectedItem.title}"/>
  32:         </mx:HBox>
  33:         <mx:HBox width="100%">
  34:             <mx:Label text="Post Body" width="60" />
  35:             <mx:TextArea id="body" text="{blogPosts.selectedItem.body}"/>
  36:         </mx:HBox>
  37:         <mx:ControlBar>
  38:             <mx:Button label="Create" click="blogPostCreateEvent(event)" />
  39:             <mx:Button label="Update" click="blogPostUpdateEvent(event)" />
  40:             <mx:Button label="Destroy" click="blogPostdestroyEvent(event)" />
  41:         </mx:ControlBar>
  42:     </mx:Panel>
  43: </mx:Application>

  • BlogPost.as
   1: package
   2: {
   3:     import mx.collections.ArrayCollection;
   4:     import mx.core.UIComponent;
   5:     
   6:     import org.rubyamf.remoting.ssr.FaultEvent;
   7:     import org.rubyamf.remoting.ssr.RemotingService;
   8:     import org.rubyamf.remoting.ssr.ResultEvent;
   9:             
  10:     [RemoteClass(alias='BlogPost')]
  11:     public class BlogPost
  12:     {
  13:         public var id:int;
  14:         public var title:String; 
  15:         public var body:String;
  16:         public var simpleErrors:Object;
  17:         
  18:         [Bindable]
  19:         public static var blogPosts:ArrayCollection;
  20:         public static var parentContainer:UIComponent;
  21:         private var service:RemotingService = new RemotingService("/rubyamf/gateway", "BlogPostsController");
  22:         
  23:         public function BlogPost(title:String = "", body:String = ""){
  24:             this.title = title;
  25:             this.body  = body;
  26:         }
  27:         // index methods
  28:         public static function getBlogPosts():void{
  29:             (new BlogPost()).getBlogPosts();
  30:         }
  31:         private function getBlogPosts():void{
  32:             service.index([], onIndexResult, onFault, BlogPost);
  33:         }
  34:         private function onIndexResult(event:ResultEvent):void{
  35:             BlogPost.blogPosts = new ArrayCollection(event.result as Array);
  36:         }
  37:         // create methods
  38:         public function create():void{
  39:             service.create([this], onCreateResult, onFault, BlogPost);
  40:         }
  41:         private function onCreateResult(event:ResultEvent):void{
  42:             var blogPost:BlogPost = event.result as BlogPost;
  43:             if(BlogPost.count(blogPost.simpleErrors) == 0)
  44:                 BlogPost.blogPosts.addItem(blogPost);
  45:             else
  46:                 showErrors(blogPost.simpleErrors);
  47:         }
  48:         // update methods
  49:         public function update():void{
  50:             service.update([this], onUpdateResult, onFault, BlogPost);
  51:         }
  52:         private function onUpdateResult(event:ResultEvent):void{
  53:             var blogPost:BlogPost = event.result as BlogPost;
  54:             if(BlogPost.count(blogPost.simpleErrors) == 0)
  55:                 BlogPost.blogPosts[BlogPost.blogPosts.getItemIndex(this)] = blogPost;
  56:             else
  57:                 showErrors(blogPost.simpleErrors);
  58:         }
  59:         // update methods
  60:         public function destroy():void{
  61:             service.destroy([this], onDestroyResult, onFault, BlogPost);
  62:         }
  63:         private function onDestroyResult(event:ResultEvent):void{
  64:             var blogPost:BlogPost = event.result as BlogPost;
  65:             BlogPost.blogPosts.removeItemAt(BlogPost.blogPosts.getItemIndex(this));
  66:         }
  67:         
  68:         // common methods
  69:         private function showErrors(errors:Object):void{
  70:             for(var error:String in errors){
  71:                 var field:UIComponent =  parentContainer[error] as UIComponent;
  72:                 field.errorString = (errors[error] as Array).join('\r');
  73:             }
  74:         }
  75:         private static function count(object:Object):int{
  76:             var i:int = i;
  77:             for(var obj:* in object) i++;
  78:             return i;
  79:         }
  80:         private function onFault(event:FaultEvent):void{
  81:             trace(event.fault);
  82:         }
  83:     }
  84: }

 

  • BlogPostsController
   1: class BlogPostsController < ApplicationController
   2:   before_filter :filter_attributes, :only=> [:create, :update]
   3:  
   4:   def filter_attributes
   5:     params[:blog_post] && params[:blog_post][:attributes] && params[:blog_post] = params[:blog_post][:attributes]
   6:   end
   7:   
   8:   # GET /blog_posts
   9:   # GET /blog_posts.xml
  10:   def index
  11:     @blog_posts = BlogPost.find(:all)
  12:  
  13:     respond_to do |format|
  14:       format.html # index.html.erb
  15:       format.amf  { render :amf => @blog_posts }
  16:       format.xml  { render :xml => @blog_posts }
  17:     end
  18:   end
  19:   
  20:   # GET /blog_posts/1
  21:   # GET /blog_posts/1.xml
  22:   def show
  23:     @blog_post = BlogPost.find(params[:id])
  24:  
  25:     respond_to do |format|
  26:       format.html # show.html.erb
  27:       format.xml  { render :xml => @blog_post }
  28:     end
  29:   end
  30:  
  31:   # GET /blog_posts/new
  32:   # GET /blog_posts/new.xml
  33:   def new
  34:     @blog_post = BlogPost.new
  35:  
  36:     respond_to do |format|
  37:       format.html # new.html.erb
  38:       format.xml  { render :xml => @blog_post }
  39:     end
  40:   end
  41:  
  42:   # GET /blog_posts/1/edit
  43:   def edit
  44:     @blog_post = BlogPost.find(params[:id])
  45:   end
  46:  
  47:   # POST /blog_posts
  48:   # POST /blog_posts.xml
  49:   def create
  50:     @blog_post = BlogPost.new(params[:blog_post])
  51:  
  52:     respond_to do |format|
  53:       if @blog_post.save
  54:         flash[:notice] = 'BlogPost was successfully created.'
  55:         format.html { redirect_to(@blog_post) }
  56:         format.amf  { render :amf => @blog_post }
  57:         format.xml  { render :xml => @blog_post, :status => :created, :location => @blog_post }
  58:       else
  59:         format.html { render :action => "new" }
  60:         format.amf  { render :amf => @blog_post }
  61:         format.xml  { render :xml => @blog_post.errors, :status => :unprocessable_entity }
  62:       end
  63:     end
  64:   end
  65:  
  66:   # PUT /blog_posts/1
  67:   # PUT /blog_posts/1.xml
  68:   def update
  69:     @blog_post = BlogPost.find(params[:id])
  70:  
  71:     respond_to do |format|
  72:       if @blog_post.update_attributes(params[:blog_post])
  73:         flash[:notice] = 'BlogPost was successfully updated.'
  74:         format.html { redirect_to(@blog_post) }
  75:         format.amf  { render :amf => @blog_post }
  76:         format.xml  { head :ok }
  77:       else
  78:         format.html { render :action => "edit" }
  79:         format.amf  { render :amf => @blog_post }
  80:         format.xml  { render :xml => @blog_post.errors, :status => :unprocessable_entity }
  81:       end
  82:     end
  83:   end
  84:  
  85:   # DELETE /blog_posts/1
  86:   # DELETE /blog_posts/1.xml
  87:   def destroy
  88:     @blog_post = BlogPost.find(params[:id])
  89:     @blog_post.destroy
  90:  
  91:     respond_to do |format|
  92:       format.html { redirect_to(blog_posts_url) }
  93:       format.amf  { render :amf => @blog_post }
  94:       format.xml  { head :ok }
  95:     end
  96:   end
  97: end

Please note the before filter in the rails controller which is converting the params in appropriate format so that rest of the code continues to work seamlessly.

 

Here is what the output looks like when you run the application

image

 

Download code for upto this point:

  1. Rails Code
  2. Flex Code

 

So the steps taken in completing this application were:

  1. Creating a Rails app
  2. Install RubyAmf plugin
  3. Configure the RubyAmf plugin
  4. Create a Flex app
  5. Install the appropriate libraries in it
  6. Create and refactor code

 

Questions and comments are welcome :)

Wednesday, November 12th, 2008 adobe, crud, flex, open source, ruby on rails, user interface

8 Comments to Using RubyAmf for creating a CRUD application in Rails

  1. [...] Right after lunch, was the one of the most awaited sessions of the day. Gaurav built an AIR client for his fictitious app called Blabber! It is just a co-incidence that the application name sounds like Yammer. You can see the details of his presentation on his blog here. [...]

  2. Geekeerie Blog » Blog Archive » Reporting the first full day Ruby Event in India: The Ruby FunDay on November 25th, 2008
  3. Thanks for the tutorial. I am trying it with Rails 2.3.2 (using Eclipse and Aptana RadRails as a development environment. However, I done the various plugins via command line and have gotten rubyamf working with other demos).

    I get the following error at the end of step one:

    Error #2044: Unhandled CONNECTION_ERROR:. text=

    and so never get the object returned. Using mongrel server and the url for the test app is: http://Localhost:3000/bin/amf_demo.html.

    Any ideas. Would like to progress through the rest of the demo. Thanks.

  4. Mark on April 8th, 2009
  5. @Mark:
    This error is probably related to a non-existent url.
    Please see here.
    Also did you try the sample code that I provided in the blog post? and did that work?

  6. Gaurav on April 8th, 2009
  7. @Guarav:

    Upon further testing, it turns out that the problem is using port 3000 instead of 80. http://localhost:3000/bin/amf_demo.html doesn’t work when the ruby server is on port 3000. http://localhost/bin/amf_demo.html does when the ruby server is on port 80. Seems to be something in the ssr classes(?). I am not a RemoteObject expert in any way.

    This is a pain since I want to leave my Apache server running on port 80 on my development box.

    Also, using a different port with different approaches to rubyamf (other tutorials) had no problem and it is something Peter Armstrong demos in “Flexible Rails.”

    Any ideas how to fix this so it will work with a different port. Thanks.

    Mark

  8. Mark on April 8th, 2009
  9. [...] Using RubyAmf for creating a CRUD application in Rails [...]

  10. BLOG: Division durch Null » Blitzschneller Rails-Flash-Datenaustausch via AMF on October 25th, 2009
  11. Many THX for this toturial. For my first AMF-steps it was an great help! For all German-speaking interested people i advice the current railsway-magazin. It include a paper with the headline “As quick as a flash – Blitzschneller Rails-Flash-Datenaustausch via AMF”. More Infos: http://blog.division-durch-null.de/2009/10/blitzschneller-rails-flash-datenaustausch-via-amf/

    Have a lot fun!

  12. DIVISION-DURCH-NULL on November 7th, 2009
  13. Awesome tutorial, thanks you.
    I also bumped to the previous problem with

    Error #2044: Unhandled CONNECTION_ERROR:. text=

    error.

    What I did is in BlogPost.as I replace line with
    RemotingService(“/rubyamf/gateway”, “BlogPostsController”)

    to

    RemotingService(“http://localhost:3000/rubyamf/gateway”, “BlogPostsController”)

    Of course its hardcoded and we don’t want this, but rails can pass hostname and port number to flex and then we can pass them into BlogPost.as and build RemoteService url.

    Cheers Alex

  14. Alex on December 1st, 2009
  15. I know I’m a few months late but…
    @Mark
    I had the same issue as you with #2044 error.
    I managed to get it to work(in a development environment) by
    adding my localhost path+port to the Remoting Service call.
    So in your .mxml file change it to this:

    private var localService:String = “http://localhost:3001″
    private var service:RemotingService = new RemotingService(localService+”/rubyamf/gateway”, “BlogPostsController”);

    Of course, useyour localpath as the data for localService.
    In production, i might as well just use the serviceconfigs.xml file which allows me to change server settings without having to reompile
    cheers
    erick

  16. erick on December 7th, 2009

Leave a comment