Skip to content

Getting started with RestfulX and Ruby On Rails

thetron edited this page Aug 14, 2010 · 10 revisions

Introduction

In this tutorial we will build a new Rails application from scratch using the RestfulX Framework. Our application should look like this:


Assumptions

The following steps have been tried with Flex Builder 3 (and command line tools) on Mac OS X.

You should have Ruby on Rails 2.1 (or higher) and MySQL 5.0+ installed for the Ruby on Rails portion of this tutorial and Flex Builder 3 if you intend to edit your Flex code using it.

  1. Make sure you have added Flex SDK bin directory to your $PATH variable so that you can invoke mxmlc from the command line) if necessary.

Developing a Flex application that talks to Ruby on Rails with RestfulX Framework

This application will be called pomodo. First, we’ll create the standard Ruby On Rails application.

$>rails -d mysql pomodo
$>cd pomodo

Next, we will install the restfulx gem:

$>sudo gem install restfulx

Now, we need to go and set-up our application to use the restfulx gem we just installed.

Edit config/environment.rb and add:

config.gem "restfulx"

Next, we run the rx_config generator to create the appropriate Flex directories and create default restfulx configuration:

$>./script/generate rx_config

Next, we proceed to scaffold our application.

Using YAML to scaffold your Flex + Rails application

Scaffolding is a cool way to get started with a Rails (and now Flex) applications quickly. Unfortunately, things tend to become quite cumbersome once you get beyond 5 or so models. You have to individually run scaffolding for each model and then go and edit relationships in both rails and flex code. This approach just doesn’t scale. You might tolerate doing all this manual work for a few models, but what are you going to do when you have to run pretty much the same command 30 or 40 times and then remember how all these things relate to each other? Wouldn’t it be better if you could specify the bulk of your data model in some easy to read file and just run that once?

This is basically the intuition behind a generator that comes with restfulx gem. It’s called rx_yaml_scaffold.

Now comes the cool stuff. Let’s create a file called db/model.yml that contains the following:

project:
 - name: string
 - notes: text
 - start_date: date
 - end_date: date
 - completed: boolean
 - belongs_to: [user]
 - has_many: [tasks]

location:
 - name: string
 - notes: text
 - belongs_to: [user]
 - has_many: [tasks]

task:
 - name: string
 - notes: text
 - start_time: datetime
 - end_time: datetime
 - completed: boolean
 - next_action: boolean
 - belongs_to: [project, location, user]

note:
 - content: text
 - belongs_to: [user]

user:
 - login: string
 - first_name: string
 - last_name: string
 - email: string
 - has_many: [tasks, projects, locations]
 - has_one: [note]

This should be fairly self explanatory except for a few details you might not have seen in YAML documents before.

  1. You can specify most of the aspects of the models (including relationships) directly in the YAML file:
    1. Use belongs_to: [<references here>] notation (e.g. belongs_to: [user]) to refer to the belongs_to end of the relationship.
    2. has_one: following by an array of model names to denote has_one end of relationship (e.g. has_one: [note])
    3. has_many: works the same way has_one and belongs_to do.
  2. - in front of every attribute line preserves the exact order of elements in generated code. Make sure you add it!

That’s pretty much all there is to it. If your db/model.yml file contains the text above, you can run:

$>./script/generate rx_yaml_scaffold

If you have an existing Rails application and you want to generate Flex artifacts only, use “rake db:schema:to_rx_yaml”. This will generate a model.yml file for you based on your existing Rails schema.rb file. (If you’re not sure your schema.rb is up to date, run “rake db:schema:dump” to refresh schema.rb before you run db:schema:to_yaml.) Once you have a model.yml file simply run ./script/generate rx_yaml_scaffold --flex-only from your Rails app root.

And watch scaffolding fly by on the console.

Check out the Flex and Rails code after you run the command. It should have all the fields and relationships set up. This means no more extra manual labour to get your application into a runnable state. Just load some data by running:

$>rake db:refresh

If you have added Flex SDK bin directory to your $PATH variable run:

$>rake rx:flex:build

This will compile your new Flex application and move generated .swf file into the public/bin directory.

If you DON’T have mxmlc executable accessible from the command line you’ll have to open this project in Flex Builder and compile it.

Next, start the server by running:

$>./script/server

And point your browser at http://localhost:3000. It’s not going to be the greatest Flex application ever written but for a 5 minute scaffolding job it’s definitely not bad.

Porting Pomodo to AIR

rx_config generator you’ve just seen above actually takes an optional argument, which is quite handy for converting our Flex project into an AIR project. Make sure you say Y when prompted to overwrite .actionScriptProperties, .project and Pomodo.mxml files:

$>./script/generate rx_config -a

If you don’t want to bother with opening Flex Builder you can compile and run your new AIR application by doing this:

$>rake rx:air:build
$>rake rx:air:run

If you are running Flex Builder make sure you do the following:

  1. It is recommended that you shutdown your Flex Builder before running the generator above. This generator will change a few Flex Builder specific files (such as .actionScriptProperties and .project) to include AIR specific information. This is how Flex Builder itself knows that it’s dealing with an AIR project as opposed to a Flex one. It’s not a very good idea to be changing Flex Builder specific files while it’s running.
  2. It’s also a good idea to remove the Pomodo Run definition from Flex Builder (if you have it). To do that open Run Dialog... and delete Pomodo definition. Why is this a good idea? Well, we’ve previously run our application in Flex Builder as a Flex application. Flex Builder has saved that definition in its cache and is now convinced Pomodo is a Flex application. Converting this project to an AIR project makes the stuff Flex Builder has in its cache out of date. Our application is now going to be an AIR app. As far as Flex Builder is concerned these are not the same thing and they are run differently.

OK, let’s open Flex Builder again. With Flex Builder pacified we can now get back to somewhat more intersting stuff. The generator command above will also convert your main application file to something like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:components="pomodo.components.generated.*"
  layout="vertical"
  styleName="plain"
  initialize="init()">
  <mx:Script>
    <![CDATA[
      import org.restfulx.services.AIRServiceProvider;
      import pomodo.controllers.ApplicationController;

      private function init():void {
        ApplicationController.initialize([AIRServiceProvider], AIRServiceProvider.ID, "pomodo");
      }
    ]]>
  </mx:Script>
  <mx:TabNavigator width="100%" height="100%">
    <!-- For a simple demo, put all the components here. -->
    <generated:ProjectBox/>
    <generated:TaskBox/>
  </mx:TabNavigator>
</mx:WindowedApplication>

Basically the only thing the generator changed in the code is the way our main application controller is initialized during application start-up.

OK, you are all done, hit the run button and you should see the same application now running in AIR. It will have no data of course because there is no fixtures for AIR. So go ahead and create a few tasks and projects. They are now being saved to your local AIR database called “pomodo”.

To recap: converting between a Flex application that talks to Rails server using XML-over-HTTP into an AIR application that’s using local SQL database is just a matter of running one command. A command that only changes the way our application is initialized, NOT any of the code used to actually manipulate projects and tasks.

Getting Pomodo AIR application to talk to Ruby on Rails again!

One nice thing about having much of the plumbing abstracted away is that you can now have your brand new AIR application talking to the Rails server again.

If you simply call PomodoController.initialize() with no arguments then XML-over-HTTP is going to be the default service provider. So let’s change our Pomodo.mxml code to this:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:components="pomodo.components.generated*"
  layout="vertical"
  styleName="plain"
  initialize="init()">
  <mx:Script>
    <![CDATA[
      import org.restfulx.Rx;
      import pomodo.controllers.ApplicationController;

      private function init():void {
        Rx.httpRootUrl = "http://localhost:3000/";
        ApplicationController.initialize();
      }
    ]]>
  </mx:Script>
  <mx:TabNavigator width="100%" height="100%">
    <!-- For a simple demo, put all the components here. -->
    <generated:ProjectBox/>
    <generated:TaskBox/>
  </mx:TabNavigator>
</mx:WindowedApplication>

As you might have guessed this will tell the app to stop using local AIR database and start talking to the remote Rails server again. Remember to start your Rails server using “script/server” before running this app.

Finally, to convert our AIR application back into the Flex application it used to be run rconfig generator with no arguments again:

$>./script/generate rx_config

Again, remember to delete the Pomodo Run target and shutdown Flex Builder before you do that.

Check your console or development.log if you have any problems

Here’s what you should have been seeing if all went well:

[ttys000][Stingray]$ rails -d mysql pomodo
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  config/initializers
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  script/process
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  Rakefile
      create  README
      create  app/controllers/application.rb
      create  app/helpers/application_helper.rb
      create  test/test_helper.rb
      create  config/database.yml
      create  config/routes.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_rails_defaults.rb
      create  config/boot.rb
      create  config/environment.rb
      create  config/environments/production.rb
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  script/about
      create  script/console
      create  script/dbconsole
      create  script/destroy
      create  script/generate
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  script/performance/request
      create  script/process/reaper
      create  script/process/spawner
      create  script/process/inspector
      create  script/runner
      create  script/server
      create  script/plugin
      create  public/dispatch.rb
      create  public/dispatch.cgi
      create  public/dispatch.fcgi
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/index.html
      create  public/favicon.ico
      create  public/robots.txt
      create  public/images/rails.png
      create  public/javascripts/prototype.js
      create  public/javascripts/effects.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  doc/README_FOR_APP
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
[ttys000][Stingray]$ cd pomodo/
[ttys000][Stingray]$ ls
README   app      db       lib      public   test     vendor
Rakefile config   doc      log      script   tmp
[ttys000][Stingray]$ vi config/environment.rb 
[ttys000][Stingray]$ ./script/generate rx_config
fetching 1.2.0 framework binary from: http://restfulx.github.com/releases/restfulx-1.2.0.swc ...
done. saved to lib/restfulx-1.2.0.swc
      create  lib/tasks/restfulx_tasks.rake
      create  .flexProperties
      create  config/restfulx.yml
      create  .actionScriptProperties
      create  .project
      create  html-template/history
      create  html-template/index.template.html
      create  html-template/AC_OETags.js
      create  html-template/playerProductInstall.swf
      create  html-template/history/history.css
      create  html-template/history/history.js
      create  html-template/history/historyFrame.html
      create  app/flex/pomodo/components
      create  app/flex/pomodo/controllers
      create  app/flex/pomodo/commands
      create  app/flex/pomodo/models
      create  app/flex/pomodo/events
      create  app/flex/pomodo/components/generated
      create  public/javascripts/swfobject.js
      create  public/expressInstall.swf
overwrite public/index.html? (enter "h" for help) [Ynaqdh] Y
       force  public/index.html
  dependency  rx_controller
      create    app/flex/pomodo/controllers/ApplicationController.as
      create  pomodo.tmproj
      create  app/flex/Pomodo.mxml
      create  app/flex/Pomodo-config.xml
[ttys000][Stingray]$ mate .
[ttys000][Stingray]$ ./script/generate rx_yaml_scaffold
running: rx_scaffold User 
  dependency  scaffold
      exists    app/models/
      exists    app/controllers/
      exists    app/helpers/
      create    app/views/users
      exists    app/views/layouts/
      exists    test/functional/
      exists    test/unit/
      exists    public/stylesheets/
      create    app/views/users/index.html.erb
      create    app/views/users/show.html.erb
      create    app/views/users/new.html.erb
      create    app/views/users/edit.html.erb
      create    app/views/layouts/users.html.erb
      create    public/stylesheets/scaffold.css
      create    app/controllers/users_controller.rb
      create    test/functional/users_controller_test.rb
      create    app/helpers/users_helper.rb
       route    map.resources :users
  dependency    model
      exists      app/models/
      exists      test/unit/
      exists      test/fixtures/
      create      app/models/user.rb
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      create  app/flex/pomodo/models/User.as
      create  app/flex/pomodo/components/generated/UserBox.mxml
       force  app/controllers/users_controller.rb
       force  app/models/user.rb
       force  test/fixtures/users.yml
      create  schema/migration
      create  db/migrate
      create  db/migrate/20081119102430_create_users.rb
  dependency  rx_controller
       force    app/flex/pomodo/controllers/ApplicationController.as
done ...
running: rx_scaffold Project
  dependency  scaffold
      exists    app/models/
      exists    app/controllers/
      exists    app/helpers/
      create    app/views/projects
      exists    app/views/layouts/
      exists    test/functional/
      exists    test/unit/
      exists    public/stylesheets/
      create    app/views/projects/index.html.erb
      create    app/views/projects/show.html.erb
      create    app/views/projects/new.html.erb
      create    app/views/projects/edit.html.erb
      create    app/views/layouts/projects.html.erb
   identical    public/stylesheets/scaffold.css
      create    app/controllers/projects_controller.rb
      create    test/functional/projects_controller_test.rb
      create    app/helpers/projects_helper.rb
       route    map.resources :projects
  dependency    model
      exists      app/models/
      exists      test/unit/
      exists      test/fixtures/
      create      app/models/project.rb
      create      test/unit/project_test.rb
      create      test/fixtures/projects.yml
      create  app/flex/pomodo/models/Project.as
      create  app/flex/pomodo/components/generated/ProjectBox.mxml
       force  app/controllers/projects_controller.rb
       force  app/models/project.rb
       force  test/fixtures/projects.yml
      exists  schema/migration
      exists  db/migrate
      create  db/migrate/20081119102431_create_projects.rb
  dependency  rx_controller
       force    app/flex/pomodo/controllers/ApplicationController.as
done ...
running: rx_scaffold Task
  dependency  scaffold
      exists    app/models/
      exists    app/controllers/
      exists    app/helpers/
      create    app/views/tasks
      exists    app/views/layouts/
      exists    test/functional/
      exists    test/unit/
      exists    public/stylesheets/
      create    app/views/tasks/index.html.erb
      create    app/views/tasks/show.html.erb
      create    app/views/tasks/new.html.erb
      create    app/views/tasks/edit.html.erb
      create    app/views/layouts/tasks.html.erb
   identical    public/stylesheets/scaffold.css
      create    app/controllers/tasks_controller.rb
      create    test/functional/tasks_controller_test.rb
      create    app/helpers/tasks_helper.rb
       route    map.resources :tasks
  dependency    model
      exists      app/models/
      exists      test/unit/
      exists      test/fixtures/
      create      app/models/task.rb
      create      test/unit/task_test.rb
      create      test/fixtures/tasks.yml
      create  app/flex/pomodo/models/Task.as
      create  app/flex/pomodo/components/generated/TaskBox.mxml
       force  app/controllers/tasks_controller.rb
       force  app/models/task.rb
       force  test/fixtures/tasks.yml
      exists  schema/migration
      exists  db/migrate
      create  db/migrate/20081119102432_create_tasks.rb
  dependency  rx_controller
       force    app/flex/pomodo/controllers/ApplicationController.as
done ...
running: rx_scaffold Note
  dependency  scaffold
      exists    app/models/
      exists    app/controllers/
      exists    app/helpers/
      create    app/views/notes
      exists    app/views/layouts/
      exists    test/functional/
      exists    test/unit/
      exists    public/stylesheets/
      create    app/views/notes/index.html.erb
      create    app/views/notes/show.html.erb
      create    app/views/notes/new.html.erb
      create    app/views/notes/edit.html.erb
      create    app/views/layouts/notes.html.erb
   identical    public/stylesheets/scaffold.css
      create    app/controllers/notes_controller.rb
      create    test/functional/notes_controller_test.rb
      create    app/helpers/notes_helper.rb
       route    map.resources :notes
  dependency    model
      exists      app/models/
      exists      test/unit/
      exists      test/fixtures/
      create      app/models/note.rb
      create      test/unit/note_test.rb
      create      test/fixtures/notes.yml
      create  app/flex/pomodo/models/Note.as
      create  app/flex/pomodo/components/generated/NoteBox.mxml
       force  app/controllers/notes_controller.rb
       force  app/models/note.rb
       force  test/fixtures/notes.yml
      exists  schema/migration
      exists  db/migrate
      create  db/migrate/20081119102433_create_notes.rb
  dependency  rx_controller
       force    app/flex/pomodo/controllers/ApplicationController.as
done ...
running: rx_scaffold Location
  dependency  scaffold
      exists    app/models/
      exists    app/controllers/
      exists    app/helpers/
      create    app/views/locations
      exists    app/views/layouts/
      exists    test/functional/
      exists    test/unit/
      exists    public/stylesheets/
      create    app/views/locations/index.html.erb
      create    app/views/locations/show.html.erb
      create    app/views/locations/new.html.erb
      create    app/views/locations/edit.html.erb
      create    app/views/layouts/locations.html.erb
   identical    public/stylesheets/scaffold.css
      create    app/controllers/locations_controller.rb
      create    test/functional/locations_controller_test.rb
      create    app/helpers/locations_helper.rb
       route    map.resources :locations
  dependency    model
      exists      app/models/
      exists      test/unit/
      exists      test/fixtures/
      create      app/models/location.rb
      create      test/unit/location_test.rb
      create      test/fixtures/locations.yml
      create  app/flex/pomodo/models/Location.as
      create  app/flex/pomodo/components/generated/LocationBox.mxml
       force  app/controllers/locations_controller.rb
       force  app/models/location.rb
       force  test/fixtures/locations.yml
      exists  schema/migration
      exists  db/migrate
      create  db/migrate/20081119102434_create_locations.rb
  dependency  rx_controller
       force    app/flex/pomodo/controllers/ApplicationController.as
done ...
   identical  pomodo.tmproj
overwrite app/flex/Pomodo.mxml? (enter "h" for help) [Ynaqdh] Y
       force  app/flex/Pomodo.mxml
   identical  app/flex/Pomodo-config.xml
[ttys000][Stingray]$ rake db:refresh
(in /Users/Dima/Projects/experiments/pomodo)
== 20081119102430 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0034s
== 20081119102430 CreateUsers: migrated (0.0037s) =============================

== 20081119102431 CreateProjects: migrating ===================================
-- create_table(:projects)
   -> 0.0045s
== 20081119102431 CreateProjects: migrated (0.0050s) ==========================

== 20081119102432 CreateTasks: migrating ======================================
-- create_table(:tasks)
   -> 0.0044s
== 20081119102432 CreateTasks: migrated (0.0046s) =============================

== 20081119102433 CreateNotes: migrating ======================================
-- create_table(:notes)
   -> 0.0037s
== 20081119102433 CreateNotes: migrated (0.0041s) =============================

== 20081119102434 CreateLocations: migrating ==================================
-- create_table(:locations)
   -> 0.0038s
== 20081119102434 CreateLocations: migrated (0.0041s) =========================

[ttys000][Stingray]$ rake rx:flex:build
(in /Users/Dima/Projects/experiments/pomodo)
Compiling /Users/Dima/Projects/experiments/pomodo/app/flex/Pomodo.mxml
Loading configuration file /Applications/Adobe Flex Builder 3/sdks/3.0.0/frameworks/flex-config.xml
Loading configuration file /Users/Dima/Projects/experiments/pomodo/app/flex/Pomodo-config.xml
/Users/Dima/Projects/experiments/pomodo/app/flex/Pomodo.swf (378010 bytes)
Moving /Users/Dima/Projects/experiments/pomodo/app/flex/Pomodo.swf to 
/Users/Dima/Projects/experiments/pomodo/public/bin
Done!
[ttys000][Stingray]$ ./script/server 
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.1.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.


Processing LocationsController#index (for 127.0.0.1 at 2008-11-19 12:25:45) [GET]
  Session ID: 190716e0211d29be9dcd36c51d322a0e
  Parameters: {"format"=>"fxml", "action"=>"index", "controller"=>"locations"}
  SQL (0.000226)   SET NAMES 'utf8'
  SQL (0.000078)   SET SQL_AUTO_IS_NULL=0
  Location Load (0.000365)   SELECT * FROM `locations` 
  Location Columns (0.001900)   SHOW FIELDS FROM `locations`
Completed in 0.12109 (8 reqs/sec) | DB: 0.00257 (2%) | 200 OK [http://localhost/locations.fxml]


Processing NotesController#index (for 127.0.0.1 at 2008-11-19 12:25:45) [GET]
  Session ID: 5a883bd95d84ab8890a05029203f5294
  Parameters: {"format"=>"fxml", "action"=>"index", "controller"=>"notes"}
  Note Load (0.000300)   SELECT * FROM `notes` 
  Note Columns (0.001426)   SHOW FIELDS FROM `notes`
Completed in 0.01247 (80 reqs/sec) | DB: 0.00173 (13%) | 200 OK [http://localhost/notes.fxml]


Processing TasksController#index (for 127.0.0.1 at 2008-11-19 12:25:45) [GET]
  Session ID: 87dd200501748fddcaf98cfffbb0e654
  Parameters: {"format"=>"fxml", "action"=>"index", "controller"=>"tasks"}
  Task Load (0.000325)   SELECT * FROM `tasks` 
  Task Columns (0.001808)   SHOW FIELDS FROM `tasks`
Completed in 0.01924 (51 reqs/sec) | DB: 0.00213 (11%) | 200 OK [http://localhost/tasks.fxml]


Processing UsersController#index (for 127.0.0.1 at 2008-11-19 12:25:45) [GET]
  Session ID: 6ea5349eab5dea59b8fcdff5cf34e3dc
  Parameters: {"format"=>"fxml", "action"=>"index", "controller"=>"users"}
  User Load (0.000304)   SELECT * FROM `users` 
  User Columns (0.002440)   SHOW FIELDS FROM `users`
Completed in 0.01688 (59 reqs/sec) | DB: 0.00274 (16%) | 200 OK [http://localhost/users.fxml]


Processing ProjectsController#index (for 127.0.0.1 at 2008-11-19 12:25:45) [GET]
  Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo
SGFzaHsABjoKQHVzZWR7AA==--33a1d0f56f69993bc1d69552fd372a4f9a83fa44
  Parameters: {"format"=>"fxml", "action"=>"index", "controller"=>"projects"}
  Project Load (0.000349)   SELECT * FROM `projects` 
  Project Columns (0.001927)   SHOW FIELDS FROM `projects`
Completed in 0.01663 (60 reqs/sec) | DB: 0.00228 (13%) | 200 OK [http://localhost/projects.fxml]