Pages

Monday, September 1, 2008

Associations with Ruby on Rails, Flex and RubyAMF

In this blog item I will you show how easy it is with RoR and RubyAMF to do CRUD operations on tables with associations. RoR and RubyAMF makes it very easy. I used for this blog a simple department and their employees use case.
Here is an picture of the result. A department has employees and an employee belongs to a department

First step is to create the RoR application
rails hr_app

change directory to the new application
cd hr_app

We need to edit the database configuration file
hr_app\config\database.yml

Install RubyAMF
ruby script/plugin install http://rubyamf.googlecode.com/svn/tags/current/rubyamf

Generate the department and employee table
ruby script/generate rubyamf_scaffold department
ruby script/generate rubyamf_scaffold employee


change department table configuration in hr_app/db/migrate folder

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end


change employee table also located in the hr_app/db/migrate folder

class CreateEmployees < ActiveRecord::Migration

def self.up
create_table :employees do |t|
t.integer :department_id
t.string :first_name
t.string :last_name
t.string :job
t.timestamps
end
end

def self.down
drop_table :employees
end
end

Let's create the department and employee table
rake db:migrate

We can add one to many associations in RoR
Edit hr_app/app/models/department.rb file where we will add has_many

class Department < ActiveRecord::Base
has_many :employees
end

Edit hr_app/app/models/employee.rb file where we will add belongs_to

class Employee < ActiveRecord::Base
belongs_to :department
end

The RoR configuration is ready we only have to configure RubyAMF

We can generate the class mappings and copy this to the rubyamf_config.rb configuration
ruby script/generate rubyamf_mappings

The generator detects the associations between department and employees
Copy the generated block of text into config/rubyamf_config.rb:
edit hr_app/config/rubyamf_config.rb

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:associations => ["employees"],
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.register(
:actionscript => 'Employee',
:ruby => 'Employee',
:type => 'active_record',
:associations => ["department"],
:attributes => ["id", "first_name", "last_name", "job", "created_at", "updated_at", "department_id"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end

Now we can start the RoR application
ruby script/server

Create a new Flex application.
First we will create the employee and department actionscript class.
The employee field in the Department class is an arraycollection

package vo
{
import mx.collections.ArrayCollection;


[RemoteClass(alias="Department")]
[Bindable]
public class Department
{
public var id:int;
public var name:String;
public var location:String;
public var createdAt:Date;
public var updatedAt:Date;
public var employees:ArrayCollection;
public function Department()
{
}

}
}

In the Employee class we add the department field of type Department

package vo
{
[RemoteClass(alias="Employee")]
[Bindable]
public class Employee
{
public var id:int;
public var firstName:String;
public var lastName:String;
public var job:String;
public var departmentId:int;
public var createdAt:Date;
public var updatedAt:Date;
public var department:Department;
public function Employee()
{
}
}
}

Here is the mxml code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="loadAll(); loadAll2();">

<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import vo.Department;
import vo.Employee;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
[Bindable]
private var employees:ArrayCollection = new ArrayCollection();


private function loadAll():void {
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = "fill";
}
private function loadAll2():void {
var token:AsyncToken = AsyncToken(employeeService.find_all());
token.kind = "fill";
}

private function createDept():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;
var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function createDeptWithEmp():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;

var emp:Employee = new Employee;
emp.firstName = "pipo";
emp.lastName = "pipo";
emp.job = "clown"
dept.employees.addItem(emp);

var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function destroy():void {
var token:AsyncToken = AsyncToken(departmentService.destroy(dg.selectedItem.id));
token.kind = "delete";
}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
departments = event.result as ArrayCollection;
} else {
loadAll();
loadAll2();
}
}
private function resultHandler2(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
employees = event.result as ArrayCollection;
} else {
loadAll2();
}
}
]]>
</mx:Script>


<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:RemoteObject id="employeeService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="EmployeesController"
showBusyCursor="true"
result="resultHandler2(event)"
fault="faultHandler(event)" />

<mx:HBox>
<mx:VBox>
<mx:Label text="Departments with employees"/>
<mx:HBox>
<mx:Button label="Create Department" click="createDept()"/>
<mx:Button label="Create Department with Employee" click="createDeptWithEmp()"/>
<mx:Button label="Remove Department" click="destroy()"/>
</mx:HBox>
<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:DataGrid id="dg_2" dataProvider="{dg.selectedItem.employees}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
</mx:columns>
</mx:DataGrid>

</mx:VBox>
<mx:VBox>

<mx:Label text="Employees"/>

<mx:DataGrid id="dg2" dataProvider="{employees}" variableRowHeight="true">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
<mx:DataGridColumn dataField="department">
<mx:itemRenderer>
<mx:Component>
<mx:VBox>
<mx:Text text="{data.department.name}"/>
<mx:Text text="{data.department.location}"/>
</mx:VBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:VBox>

</mx:HBox>
</mx:Application>

That's all. you can easily create and retrieve departments with it's employees with RoR and RubyAMF. RoR and RubyAMF do all the hard work.

1 comment:

  1. Hello Edwin,
    great post!
    Some things where new for me, for example the command ruby script/generate rubyamf_mappings.
    Thanks for that!

    ReplyDelete