Tuesday, October 9, 2007

Update Dec. 9, 2007: I decided to get my own domain and to use Wordpress instead of Blogger so this blog has moved. The new address is panscendo.com. Any new updates to this tutorial will be here

Update Nov. 29, 2007: Tutorial now current for Rails 2.0 RC2 and RubyAMF 1.5

RubyAMF is a flash remoting gateway that allows a Rails backend to communicate with a Flex frontend. The marriage allows rapid development of rich internet applications. This tutorial goes over the basic steps of creating a RESTful Rails project that has a HTML and Flex frontend and data persistence via MySQL database.

Rails

Setting up the Rails Project

Create a new rails project by first opening a command prompt and then changing the working directory to your rails project directory. Once there type the following command to create a rails project called "rubyamf"

> rails rubyamf

Now that we have a project created we need to install RubyAMF by using the rails installer. To do this you must first move into the rubyamf directory and then run the ruby plugin installer script


> cd rubyamf
rubyamf> ruby script/plugin install http://rubyamf.googlecode.com/svn/trunk/rubyamf

Note: If you receive a error like this: "Missing the Rails 1.99.0 gem. Please 'gem install -v=1.99.0 rails', update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed." then you just need to commet out the the line specified by the error. Since Rails 2.0 is still being developed it isn't actually 1.99.0 but 1.99.blah.

Connecting Rails to your Database

Since we want to be able to save our data we need to create a database to store said data. Open up a command prompt again and, assuming MySQL is already running, type:

> mysql -u root -p
Enter password:
mysql> create database rubyamf;

Good, now we need to tell Rails how to communicate with the database we just created. Open up the config/database.yml file and edit the text to look like this:

development:
adapter: mysql
endocing: utf8
database: rubyamf
username: root
password: yourpassword
host: localhost

Creating a REST controller

We now have a Rails project that is able to talk to a database. Cool! Now what we need to do is create a controller that issues the commands. Rails allows us to do this with a little beauty command called "scaffold_resource." When we run it we always follow it with an uppercase singular noun and a series of name value pairs. The noun is the name of the Rails model, or the database table, and the name:value pairs are the names of the column in the table and their datatype. Lets try it out; open up that command prompt once again, make the rubyamf directory the active dir and then issue the following command:

rubyamf> ruby script/generate scaffold Message text:string

Note: "scaffold_resource" has been changed to "resource" in Rails 2.0.

Among other things you now have two important files; messages_controller.rb which Flex and RubyAMF will communicate with, and a migrate file (called 001_create_messages.rb) that we will run to create the database table.

Lets do the latter right now. In the command prompt type this:

rubyamf> rake db:migrate

Making Sure it all Works

We're basically finished with rails for now. But before we call it good lets just make sure that it is running smoothly (and so we can look in awe of what we just did).

Boot up your rails server:

rubyamf> ruby script/server

Now open up your favorite browser and make sure that RubyAMF is running by going to http://localhost:3000/rubyamf/gateway. You should see something like the following image.

Screenshot of RubyAMF Gateway up and running

If you do not get the above image and instead get prompted to download an AMF file you need to edit the app/controllers/rubyamf_controller.rb file. Open it up and change:

amf_response = if request.env['CONTENT_TYPE'].to_s.match(/x-amf/) 
headers['Content-Type'] = "application/x-amf"
RailsGateway.new.service(request.raw_post) #send the raw data throught the rubyamf gateway and create the response
else
welcome_screen_html # load in some stub html
end
send_data(amf_response, :type => 'application/x-amf')

To:

amf_response = if request.env['CONTENT_TYPE'].to_s.match(/x-amf/) 
content_type = "application/x-amf"
RailsGateway.new.service(request.raw_post) #send the raw data throught the rubyamf gateway and create the response
else
content_type = "text/html"
welcome_screen_html # load in some stub html
end
send_data(amf_response, :type => content_type, :disposition=>'inline')

Note: If you use the rubyamf controller fix suggested on the RAMF blog it will brake your application (thanks goes to Quest4 for figuring this one out). The above code is not official so use it at your own risk.

Now make sure our Messages controller is working by pointing your browser to http://localhost:3000/messages and witnessing this:

Screenshot of message controller

So far so good. Now what we need to do is to add some data into the database so our Flex app has something to retrieve. Lets do that by clicking the "New Message" link at http://localhost:3000/messages, typing a short message (this tutorial uses "Hello World"), and press the "Create" button. *Bam* you just added a new Message to the database.

Enough of Rails, lets dive into Flex

Flex

Setting up the Flex Project

Boot up Adobe Flex and create a new project. I named mine rubyamf for consistency but you could name it something else (like planctomyces). Click the "Next" button, not the "Finish" button. Set the output folder to the "bin" dir (you'll probably have to create this) in the "public" dir of your rails project. That is, \rubyamf\public\bin. Press "Next." Now make sure that your output folder URL is set to: http://localhost:3000/bin. Press "Finish." You now have a shiny new Flex project.

Adding services-config.xml

We need to create an XML file that will point your Flex project in the direction of your Rails project. This is called the "services-config.xml" file and it needs to be placed in the same folder as your MXML file. Create a new file via File > New > File and name it "services-config.xml"

Now for the code. Open up "services-config.xml" and add this code:

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service id="rubyamf-flashremoting-service" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage">
<destination id="rubyamf">
<channels>
<channel ref="rubyamf"/>
</channels>
<properties>
<source>*</source>
</properties>
</destination>
</service>
</services>
<channels>
<channel-definition id="rubyamf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://localhost:3000/rubyamf/gateway" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>

Now we tell Flex to include "services-config.xml" when it compiles the application. To do this we need to add a command to the flex compiler. Do this by Project > Properties > Flex Compiler and adding -services "services-config.xml" to the "Additional compiler arguments" field.

Screenshot showing additional compiler arguments field

Creating the UI

Now the fun part. Here's the Flex UI code to paste into the MXML file:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:TextArea id="resultTxt"
width="80%" height="30%" />
<mx:Button id="getMessage"
label="Get Messages" />
<mx:TextArea id="createTxt"
width="80%" height="30%" />
<mx:Button id="createMessage"
label="Create Message" />
</mx:Application>

The above should look something like this:

Screenshot of flex ui

The above is pretty and all but it's like a mannequin; looks good but it has no function. Lets give it a purpose and turn it into a real live lady. We'll do this by adding the RemoteObject code.

Between the <mx:Application> and <mx:TextArea /> tags add this:

 <mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.Fault;

private function onFault(e:FaultEvent):void
{
resultTxt.text = e.fault.faultString;
}
private function onResult(e:ResultEvent):void
{
resultTxt.text = e.message.toString();
}
]]>
</mx:Script>

<mx:RemoteObject id="messageService" fault="onFault(event)"
source="MessagesController" destination="rubyamf">
<mx:method name="index" result="onResult(event)" />
<mx:method name="create" result="onResult(event)" />
</mx:RemoteObject>
  • RemoteObject is where the magic occurs. This particular instance we chose to call id="messageService"
  • source="MessagesController" is the name of the rails controller we want to communicate with. The actual name needs to be exactly the same as the ruby class name (open up app/controllers/messages_controller.rb)
  • destination="rubyamf" maps to the channel-definition in the services-config.xml file we created earlier (which then points Flex to the rubyamf remoting endpoint).
  • The two <mx:method /> tags map to two of the methods in the MessagesController class.

To call our methods we'll update the "click" event handlers of the two buttons in our UI. Add click="messageService.index.send();" to the "getMessage" button and click="messageService.create.send({text: createTxt.text});" to the "createMessage" button.

 <mx:Button id="getMessage"
label="Get Messages"
click="messageService.index.send();" />

<mx:Button id="createMessage"
label="Create Message"
click="messageService.create.send({text: createTxt.text});" />

Back to Rails

Tweaking messages_controller.rb

We're almost finished. All we have to do now is explain to our rails controller about AMF. We'll be polite and speak it's native language; ruby. Open up app/controllers/messages_controller.rb and add "format.amf { render :amf => @messages }" to the respond_to block of the index method.

 def index
@messages = Message.find(:all)

respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @messages.to_xml }
format.amf { render :amf => @messages }
end
end

The respond_to block tells our controller to act differently when different mediums are attempting to work with it. If a browser calls the index method the controller will spit out HTML but now if the Flash Player requests something the controller will spit out AMF. Perfect!

Updating the create method is a bit trickier. We add an if statement to check if AMF was sent, and if so, do some tricky stuff.

 def create
if is_amf
@message = Message.new({:text => params[0][:text]})
else
@message = Message.new(params[:message])
end
...

Note: params[0][:text] was suggested by Peter Armstrong - thanks!

Then we add some code to the respond_to block that tells the controller to speak AMF if it's talking to the flash player.

 ...
respond_to do |format|
if @message.save
flash[:notice] = 'Message was successfully created.'
format.html { redirect_to message_url(@message) }
format.xml { head :created, :location => message_url(@message) }
format.amf { render :amf => "Message Saved" }
else
format.html { render :action => "new" }
format.xml { render :xml => @message.errors.to_xml }
format.amf { render :amf => @message.errors }
end
end
end

Whew! That's it. We can test the app now.

Testing... (in awe)

First we need to start our server (or restart it).

rubyamf> ruby script/server

Compile and run your Flex project. It should be running from the URL "http://localhost:3000/bin/rubyamf.html." Click on "Get Messages" and you should see something like this:

checking if get-messages works

Type a message into the second text area ("We received data from RubyAMF, can we send data to it?"). Press the button and you should see:

checking if create-message works

Finished!


Yee-haw! By using the powers of RubyAMF we have just grabbed data from a database and written some right back at speeds much faster than XML or JSON. Plus, we still get a HTML front end for those types that fear flash.

56 comments:

Aryk Grosz said...

Just curious, at the end of your article, you say that rubyAMF is much faster than weborb for rails. I then clicked on that link provided and I don't see any comparison with weborb for rails.

How did you arrive to that conclusion?

bryanc said...

RubyAMF is faster than XML and JSON because it uses AMF. RubyAMF is faster than WebORB for Rails because you have the option of not using active records. Since the tutorial used AR the mentioned statement was misleading. Thanks for the catch.

rekab said...

Great article! -- Thanks!!

If you have time, I'd love to see this example done with Cairngorm

S. said...
This comment has been removed by the author.
Peter said...

Hi,

I really like your tutorial style; we write in a similar way.

Note that I think there is a small bug: the text of the messages that are created from Flex is NULL. (At least this was true for me using Rails 2 and the mixdev RubyAMF branch, installed with ruby script\plugin install http://rubyamf.googlecode.com/svn/branches/mixdev/rubyamf.)

There is one change you need to make in messages_controller.rb:
...
def create
if is_amf
# @message = Message.new({:text => params[:text]}) #replace this with the following
@message = Message.new({:text => params[0][:text]})
else
...

I don't know if this happens with Rails 1.2.x and the trunk RubyAMF release.

Cheers,
Peter Armstrong

bryanc said...

@peter - I created the tutorial with Rails 1.2.3 and RAMF 1.3.3. If you installed RAMF fairly recently with the path given in your comment then you downloaded the 1.3.5 release (which is unstable). The RAMF team is doing a huge upgrade [http://blog.rubyamf.org/?p=80] right now so things are sure to be a bit wacky.

It's good that you caught the above mentioned bug though. For RAMF 1.3.3 Aaron stated [href="http://blog.rubyamf.org/?p=50] that "any objects properties whether it be a basic object, or value object get merged into the params hash. ONLY the first parameter gets merged though." So this should include params[:text]. Given your comment it seems that RAMF may not work that way anymore?

I'll be sure to update the tutorial once 1.3.5 is released, so keep those good comments/bugs coming!

And... I hope that you perusing RAMF tutorials is evidence that you will cover RAMF in your book (so far it has been very valuable to me).

Peter said...

Hi Bryan,

Yeah, chapter 10 is now going to be pomodo on RubyAMF--hopefully it will be a good complement to this tutorial. (This will be prominently linked from it.) Also, I checked with Aaron and the params[0] is intentional behavior in RubyAMF, so you'll want to update your tutorial at some point.

Thanks again...

Cheers,
Peter

Bill Christian said...

Good introduction tutorial. I was able to get the example running with little issue at all. I've expanded the example to pass a user id as well.

However, I have a follow up question. Your example builds an array on the fly to pass in the messageService.create.send invocation. How can I expand this to pass an object or an array of objects and have them processed by Rails? Is there were I need to build a DTO or is there a simple solution?

If you have a helpful reference, please pass along.

bryanc said...

@bill - Yeah, definitely use a DTO if you want to pass complex types with RAMF. RAMF has good class mapping so it's fairly easy to implement. Once I find time (haha) I'll write up another tutorial that goes over DTO.

As for resources:
* RAMF blog (http://blog.rubyamf.org), especially the comments where Aaron gives great feedback to some good user questions
* Google Groups (http://groups.google.com/group/rubyamf) - The RAMF core team is great at answering questions very quickly. Plus you get to be on the bleeding edge and chat with the big wigs :)
* Google Code (http://code.google.com/p/rubyamf/downloads/list) - I actually just noticed that there are zip files to download which seem to be code examples.

Good Luck!

quest4 said...

Hi Bryan have you ported this example over to Rails 2.0 and rubyamf 1.5? I am getting a ':type option required' error followed by the list of files starting with 'send_file_headers!' methods.
The error '-e:2:in 'load'' completes the error.

Your example was a great help for the previous versions but I am now lost in the woods. Anybody with any help would be much appreciated.

bryanc said...

I've been waiting for the stable release of 1.5 and for RAMF to officially support Rails 2.0 before updating the tutorial. But, perhaps I should just be bold and do it now? If the weather in Seattle ever stops being so nice I'll have time to stay indoors and get some work done... :)

quest4 said...

Then I'm a praying for some rain in Seattle this weekend!

Anonymous said...

the web will <3 you for updating it.

bryanc said...

Well folks, I tried to update the tutorial for Rails 2.0 and RAMF 1.5 but I ran into a bug. Basically, in the tutorial we tell rails to send an anonymous object to the flash player but RAMF doesn't know how to do that. To fix this we need to use something called class mapping which is a means of mapping a server side (ruby) class to a client side (as3) class.

This is just a bug. And it really isn't something that should stop anyone from using RAMF. Rails 2.0 is still in beta so RAMF 1.5 doesn't officially support it. I'm confident that once Rails 2.0 final is released this mess will be taken care of.

If anyone is having troubles getting RAMF running with Rails 2.0 I suggest you read Chapter 11 of Peter Armstrong's great Flexible Rails book. Peter was nice enough to make it public so you can grab it here: http://rubyamf.googlecode.com/files/FlexibleRailsIteration11.zip

Really, read it. It's amazing.

bryanc said...

Whoops. The above link is to the code, not the chapter. However, Peter is trying to release a PDF of the chapter soon. I'll be sure to let everyone know when he does.

I really didn't make a mistake, I just wanted to post another comment :)

quest4 said...

My problem above was a result of the fix in the rubyamf controller which allowed you to test and render the gateway in html.
Once this fix was reversed I could send objects from rubyamf to flex without a problem (no server side class mapping), however updating objects to rubyamf from flex is not as straight forward, and yes may require server side class mapping.

bryanc said...

By golly, you're a master of all things.

I think you isolated where the bug is occurring. I'm going to inform the troops so I'll be quoting you on the RAMF google group.

bryanc said...

I think the problem is that rails can't get a value from headers['Content-Type']. But using "content-type" works. So if you want anonymous objects and the rubyamf/gateway to spit out HTML change the code to:

amf_response = if request.env['CONTENT_TYPE'].to_s.match(/x-amf/)
content_type = "application/x-amf"
RailsGateway.new.service(request.raw_post) #send the raw data
throught the rubyamf gateway and create the response
else
content_type = "text/html"
welcome_screen_html # load in some stub html
end

#render the AMF
send_data(amf_response, :type =>
content_type, :disposition=>'inline')

quest4 said...

Yeah I think the main problem in the rubyamf controller is the following code:
#if not flash user agent, send some html content
amf_response = if request.env['CONTENT_TYPE'].to_s.match(/x-amf/)

quest4 said...

The amf_response is returning true even for a html call.

bryanc said...

Hmmm... try clearing your cache perhaps?

atlantageek said...

I am converting some code from soap to rubyamf. This is my first experience with amf or whever the protocol is called. Anyway one thing that has me concerned is the hardcoded ip address of services-config.xml. This code is for a product that will be on multiple web servers. I don't want to hardcode the flex application to connect to one IP. How would I handle this in the services-config.xml file.

Brian said...

Great article! I got everything up and running in a short time.

One thing to improve:

@message = Message.new({:text => params[0][:text]})

can be replaced with

@message = Message.new(params[0])

the params name must match rails <-> flex

this saves on a lot of typing if you are sending over and object with a lot of params.

NJ said...

Is RubyAMF still alive? Looks pretty quiet out there. I am hesitant to use it if it is dead.

Lots in Costa Rica said...

I recently came across your blog and have been reading along. I think I will leave my first comment. I don’t know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.

taebo training said...

Thanks for helping me achieve this installation, in my case was a success, no doubt I'll be visiting your updates!

teeter hang ups said...

this blog was an excellent contribution to the engineers, I think are basic but very useful things, I congratulate you and the reason to keep going ..

Anonymous said...

hi guys

smtlaissezfaire said...

Thanks for the tutorial.

I've uploaded the code with some slight modifications to github:

http://github.com/smtlaissezfaire/rubyamf_example

smtlaissezfaire said...
This comment has been removed by the author.
Send flowers to poland said...

I suggest this site to my friends so it could be useful & informative for them also. Great effort.

Flowers store UK said...

Really like this website, this really helps and very useful.

louis said...

I think its a pretty good idea not that there aren't other sites out there that already do something similar but I think it might catch on here pretty well.

louis said...

I think its a pretty good idea not that there aren't other sites out there that already do something similar but I think it might catch on here pretty well.

Anonymous said...

Men’s Louis vuitton handbag is the Buy Replica Handbags representative of status an taste. We all know that louis vuitton replicas Beverly MM Monogram Multicolore Jimmy Choo handbags Noir is belong to women, but Louis Vuitton City Bag GM Monogram Etoile not only for women, men can be Graphic Design the part of Louis vuitton begdke100831 .

antywong said...

In the new Louis Vuitton Cabas Rivington core values campaign ,the ads features astronauts photoshop software Buzz Aldrin who is the first woman astronaut into space, Jim Lovell the director of Apollo 13 and Sally Ride the first louis vuitton moda astronaut who left his footprint in the moon. The three astronauts pose buy replica handbags with a battered pickup truck and a travel bag while gazing up into the sky in the California desert nrgbh100901.

Sildenafil Citrate said...

hi I didn't know you move to wordpress, but thanks for letting us know, I will go to your new site and I hope it's like this one or even better!

Firesales Costa Rica said...

Thank you for sharing information. It is quite useful for us also. I always love to read such type of things.

eco tours said...

Hello people want to express my satisfaction with this blog very creative and I really like the views of the focus very good indeed Thank you for the helpful information. I hope you keep up the good work on making your blog a success!

Anonymous said...

i m getting error like this while installing AMF
===
C:\ruby\rubyamf>ruby script/plugin install http://rubyamf.googlecode.com/svn/tag
s/1.6.5/rubyamf/
Plugin not found: ["http://rubyamf.googlecode.com/svn/tags/1.6.5/rubyamf/"]
====

youlacka said...

That is really very good article.kızlarla chat,I am glad to know.mersin chat,Thanks for sharing !avrupa chat

youlacka said...
This comment has been removed by the author.
Handwriting analysis said...

You got a really useful blog. I have been here reading for about an hour. I am a newbie and your success is very much an inspiration for me.

sophie said...

I have checked on the link that will allow me to see updates, but can't seem to find any. Is there any update lately, though?
phentermine 37.5

Web Hosting India said...

It’s really a great and helpful piece of information. I’m glad that you shared this helpful information with us. Please keep us up to date like this. Thanks for sharing.

Free Cloud Hosting said...

Nice article. Thanks for sharing it.

Free Hosting said...

Wonderful article. It was useful and interesting. Thanks for sharing it.

website registration india said...

You made certain good points there. I did a search on the subject matter and found the majority of people will have the same opinion with your blog.

Mobile Application said...

My friend recommended to this blog.... you have some awesome articles shear. keep it up the great work

android application | android web application

instantinsurance said...

Wow, nice post,there are many person searching about that now they will find enough resources by your post.Thank you for sharing to us.Please one more post about that..Sipos

Sneha said...

I've been suffering how to get such an information about this soft ware after watching this ooohh this is the right siteto approach.

obat herbal said...

Glad to visit your site. An awesome blog. Nice Information It's really very informative that I wanted ever, thanks for this. Obat Herbal Bali Ratih Obat Jerawat Tulisan Indah Suplemen Fitnes Murah

oraclefusion said...

I found this blog very informative and good points were mentioned in this article for the further information visit our site
Oracle Fusion

oracleR12 said...

Hi,
It’s really a great and helpful piece of information. I’m glad that you shared this useful information with us. Please keep us up to date like this. Thanks for sharing.the way of your presentaion is too good.
Thank you.
oracle EBS training

ERP said...


Oracle fusion HCM Training from ERPTREE gives you the best results to learn your dream course and maintains sufficient knowledge on oracle. It provides training by self-paced videos which are very helpful for the users to watch at any time according to their schedule. It is globally accepted and having many users undergoing training every day.


Oracle fusion HCM Online Training

Oracle Fusion HCM Training

Calfre India said...

I really appreciate information shared above. It’s of great help. If someone want to learn Online (Virtual) instructor
lead live training in Oracle Fusion Financials online Training , World Class Virtual Instructor led training on Oracle fusion Financials Training. We have industry expert trainer. We provide Training Material and Software Support. calfre has successfully conducted 100000+ trainings in Hyderabad, Ameerpet, Mumbai, Chennai, India, USA, UK, Australlia,
Switzerland, Qatar, Saudi Arabia, Bangladesh, Bahrain and UAE etc.

Post a Comment