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:
1: > ruby script/plugin install http://rubyamf.googlecode.com/svn/tags/current/rubyamf
Step 1: Lets try if everything is working fine by creating a new Flex Project
Open Flex Builder and create a new Flex Project
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:
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.
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.
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:
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:
Download code for upto this point:
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:
Download code for upto this point:
- Rails Code (no changes)
- 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:
- Rails Code (no changes)
- 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
Download code for upto this point:
So the steps taken in completing this application were:
- Creating a Rails app
- Install RubyAmf plugin
- Configure the RubyAmf plugin
- Create a Flex app
- Install the appropriate libraries in it
- Create and refactor code
Questions and comments are welcome :)
8 Comments to Using RubyAmf for creating a CRUD application in Rails
Leave a comment
Blogroll
Recent Posts
- Autocompleting ssh, rake, cap command parameters using PowerShell
- Using RubyAmf for creating a CRUD application in Rails
- Multiple ways to open PowerShell in the current Explorer window
- Context sensitive auto-completion using PowerShell, PowerTab and GIT
- Displaying GIT Branch on your PowerShell prompt
XBox Live
Songs I like
Categories
- adobe (4)
- amarok (1)
- C# (2)
- chat application (1)
- crud (2)
- cygwin (1)
- explorer (1)
- expression blend (1)
- flex (4)
- FOSS (2)
- free (1)
- GIT (3)
- ID3 (1)
- java (1)
- microsoft (3)
- mxml (2)
- nokia (1)
- open source (5)
- perl (1)
- powerpoint (2)
- powershell (3)
- rails (3)
- right click (1)
- ruby (9)
- ruby on rails (3)
- security (3)
- silverlight (4)
- smart playlist (1)
- socket (1)
- software (7)
- ssh (1)
- sudo (1)
- tabs (1)
- tomcat (1)
- user interface (6)
- vista (9)
- vista, security (2)
- visual studio 2008 (1)
- win32ole (1)
- windows (13)
- windows media player (1)
- wpf (1)
- xaml (4)
- XP (3)

[...] 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. [...]
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.
@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?
@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
[...] Using RubyAmf for creating a CRUD application in Rails [...]
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!
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
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