-
Notifications
You must be signed in to change notification settings - Fork 62
Using CouchDB in Flex AIR with RestfulX
RestfulX framework allows you to develop standalone Flex and AIR applications that integrate directly with CouchDB using the same set of high-level REST concepts you use when talking to Ruby on Rails or SQLite that comes with AIR.
Standard RestfulX tutorials always seem to start with developing a Flex application. Let’s break with tradition and create an AIR application first this time.
While it’s possible to write the entire application by hand, you can use a few handy generators that come with restfulx gem to help you get up and running faster. Note, there’s no need to install/or run Rails, Merb, etc. Ruby is a great language to use for code generation. So it is used to create a basic Rakefile and add a few RubiGen based generators to help you along the way. It is possible to open/edit any of these projects in Flex Builder 3 if you want to. In fact, when you use rx-gen generator project files for Flex Builder 3 and TextMate are created by default.
What you’ll need:
- CouchDB 0.8.1 + (Best of luck if you are building this from source on something other than Linux :))
- Flex SDK 3.0 +
- Ruby 1.8.6 + (for code generation, not required to use/run the application)
Before you start you might want to add Flex SDK bin folder to your $PATH variable if you haven’t already. This will allow you to invoke commands such as mxmlc
from the command line and run rake tasks such as air:run
and rx:air:build
.
- On OS X it’s typically
/Applications/Adobe Flex Builder 3/sdks/3.0.0/bin
- On Win32 it’s
C:\Program Files\Adobe\Flex Builder 3\sdks\3.0.0\bin
You will also want to start your CouchDB server.
First, let’s create a project:
$>sudo gem install restfulx
$>rx-gen -a pomodo
$>cd pomodo
$>./script/generate rx_config
$>touch db/model.yml
Edit db/model.yml
to contain 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]
$>./script/generate rx_yaml_scaffold
At this point you have a standalone AIR application that is configured to use local SQLite database that comes with AIR.
$>rake air:run
Go ahead and create some records!
If you look at Pomodo.mxml file generated for you, it should look like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:generated="pomodo.views.generated.*"
paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
layout="horizontal" styleName="plain" initialize="init()">
<mx:Script>
<![CDATA[
import org.restfulx.services.air.AIRServiceProvider;
import org.restfulx.services.http.XMLHTTPServiceProvider;
import org.restfulx.Rx;
import pomodo.controllers.ApplicationController;
private function init():void {
ApplicationController.initialize([AIRServiceProvider],
AIRServiceProvider.ID, "pomodo");
}
]]>
</mx:Script>
<mx:LinkBar dataProvider="{mainViewStack}" direction="vertical" borderStyle="solid" backgroundColor="#EEEEEE"/>
<mx:ViewStack id="mainViewStack" width="100%" height="100%">
<!-- For a simple demo, put all the components here. -->
<generated:ScrapBox/>
<generated:UserBox/>
</mx:ViewStack>
</mx:WindowedApplication>
What we are going to do is change it to this:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:generated="pomodo.views.generated.*"
paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
layout="horizontal" styleName="plain" initialize="init()">
<mx:Script>
<![CDATA[
import org.restfulx.services.as3http.DirectCouchDBHTTPServiceProvider;
import org.restfulx.services.http.XMLHTTPServiceProvider;
import org.restfulx.Rx;
import pomodo.controllers.ApplicationController;
private function init():void {
ApplicationController.initialize([DirectCouchDBHTTPServiceProvider], DirectCouchDBHTTPServiceProvider.ID);
}
]]>
</mx:Script>
<mx:LinkBar dataProvider="{mainViewStack}" direction="vertical" borderStyle="solid" backgroundColor="#EEEEEE"/>
<mx:ViewStack id="mainViewStack" width="100%" height="100%">
<!-- For a simple demo, put all the components here. -->
<generated:ScrapBox/>
<generated:UserBox/>
</mx:ViewStack>
</mx:WindowedApplication>
By default RestfulX framework will try to connect to CouchDB installation at this address:
http://127.0.0.1:5984/
. If you configured your CouchDB instance differently remember to setRx.couchDBRootUrl
appropriately.
Default database name used by the framework is
rxdb
. If you want to change this setRx.couchDbDatabaseName
to something like thisRx.couchDbDatabaseName = "foobar";
Not quite :).
You need to actually create the database if it doesn’t exist. Navigate to http://127.0.0.1:5984/_utils/
and create a new database. If you stick with RestfulX defaults call the database rxdb
.
With the database created, simply run:
$>rake air:run
Congrats you are now using CouchDB for persistence from your AIR app. Go ahead and create some stuff!
OK, now that we have our AIR application talking to CouchDB, we are going to turn it into a Flex app.
Simply run the following command in your pomodo
application folder and type y
whenever prompted to overwrite files:
$>rx-gen .
Let’s run ./script/generate rx_yaml_scaffold
again to re-generate Pomodo.mxml
file.
$>./script/generate rx_yaml_scaffold
If you look at Pomodo.mxml file re-generated for you, it should look like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:generated="pomodo.components.generated.*"
paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
layout="horizontal" styleName="plain" initialize="init()">
<mx:Script>
<![CDATA[
import pomodo.controllers.ApplicationController;
private function init():void {
ApplicationController.initialize();
}
]]>
</mx:Script>
<mx:LinkBar dataProvider="{mainViewStack}" direction="vertical" borderStyle="solid" backgroundColor="#EEEEEE"/>
<mx:ViewStack id="mainViewStack" width="100%" height="100%">
<!-- For a simple demo, put all the components here. -->
<generated:LocationBox/>
<generated:NoteBox/>
<generated:ProjectBox/>
<generated:TaskBox/>
<generated:UserBox/>
</mx:ViewStack>
</mx:Application>
We are going to change it to this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:generated="pomodo.components.generated.*"
paddingBottom="8" paddingLeft="8" paddingRight="8" paddingTop="8"
layout="horizontal" styleName="plain" initialize="init()">
<mx:Script>
<![CDATA[
import org.restfulx.services.as3http.DirectCouchDBHTTPServiceProvider;
import pomodo.controllers.ApplicationController;
private function init():void {
ApplicationController.initialize([DirectCouchDBHTTPServiceProvider], DirectCouchDBHTTPServiceProvider.ID);
}
]]>
</mx:Script>
<mx:LinkBar dataProvider="{mainViewStack}" direction="vertical" borderStyle="solid" backgroundColor="#EEEEEE"/>
<mx:ViewStack id="mainViewStack" width="100%" height="100%">
<!-- For a simple demo, put all the components here. -->
<generated:LocationBox/>
<generated:NoteBox/>
<generated:ProjectBox/>
<generated:TaskBox/>
<generated:UserBox/>
</mx:ViewStack>
</mx:Application>
This should look familiar. In fact, we’ve done the exact same thing in our AIR appliction. We simply switched the service provider to CouchDB.
Again, not quite :). First you need to build the application:
$>rake rx:flex:build
That was the easy part.
Now we need a web server of some kind to serve our application. I’m lazy so I am going to use ruby for this. Here’s my little server:
require "rubygems"
require "sinatra"
begin
require 'thin'
rescue LoadError
puts 'Lose a little weight maybe?'
end
get '/' do
redirect '/index.html'
end
You can save it to any file you want (e.g. pomodo.rb
). Start it by typing ruby pomodo.rb
.
Now we need to deal with some funky crossdomain.xml
issues. Since our Flex application is going to be making requests to a domain different than its own (CouchDB server running on a different port). We need something called Socket Policy Server
to make progress. All that server does is return appropriate crossdomain.xml
file everytime it’s required. You can refer to: http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html for details.
Here’s what our crossdomain.xml
will look like:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only" />
<allow-access-from domain="*" to-ports="5984" />
</cross-domain-policy>
Now we need an actual policy server. There’s a few different configurations refered to in the Adobe DevNet article. I’ve used their standalone Python configuration in this case. Here’s the code for your convenience, you can also get it from the article.
#!/usr/bin/env python
#
# flashpolicyd.py
# Simple socket policy file server for Flash
#
# Usage: flashpolicyd.py [--port=N] --file=FILE
#
# Logs to stderr
# Requires Python 2.5 or later
from __future__ import with_statement
import sys
import optparse
import socket
import thread
import exceptions
import contextlib
VERSION = 0.1
class policy_server(object):
def __init__(self, port, path):
self.port = port
self.path = path
self.policy = self.read_policy(path)
self.log('Listening on port %d\n' % port)
try:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
except AttributeError:
# AttributeError catches Python built without IPv6
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
# socket.error catches OS with IPv6 disabled
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('', port))
self.sock.listen(5)
def read_policy(self, path):
with file(path, 'rb') as f:
policy = f.read(10001)
if len(policy) > 10000:
raise exceptions.RuntimeError('File probably too large to be a policy file',
path)
if 'cross-domain-policy' not in policy:
raise exceptions.RuntimeError('Not a valid policy file',
path)
return policy
def run(self):
try:
while True:
thread.start_new_thread(self.handle, self.sock.accept())
except socket.error, e:
self.log('Error accepting connection: %s' % (e[1],))
def handle(self, conn, addr):
addrstr = '%s:%s' % (addr[0],addr[1])
try:
self.log('Connection from %s' % (addrstr,))
with contextlib.closing(conn):
# It's possible that we won't get the entire request in
# a single recv, but very unlikely.
request = conn.recv(1024).strip()
if request != '<policy-file-request/>\0':
self.log('Unrecognized request from %s: %s' % (addrstr, request))
return
self.log('Valid request received from %s' % (addrstr,))
conn.sendall(self.policy)
self.log('Sent policy file to %s' % (addrstr,))
except socket.error, e:
self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
except Exception, e:
self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
def log(self, str):
print >>sys.stderr, str
def main():
parser = optparse.OptionParser(usage = '%prog [--port=PORT] --file=FILE',
version='%prog ' + str(VERSION))
parser.add_option('-p', '--port', dest='port', type=int, default=843,
help='listen on port PORT', metavar='PORT')
parser.add_option('-f', '--file', dest='path',
help='server policy file FILE', metavar='FILE')
opts, args = parser.parse_args()
if args:
parser.error('No arguments are needed. See help.')
if not opts.path:
parser.error('File must be specified. See help.')
try:
policy_server(opts.port, opts.path).run()
except Exception, e:
print >> sys.stderr, e
sys.exit(1)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Save this to any file (e.g. fps.py
) and start it using:
$>sudo python fps.py --file=crossdomain.xml
You’ll need to be super-user to run this (if you are on Linux/Mac) because Flash Policy Server is expected to run on port 843, which is privileged.
If you have both our little Sinatra server and the Flash Policy Server running, navigate to http://localhost:4567
and play with our Flex+CouchDB application.
Now you are really done.
While code generation is a quick way to get started, it falls far short of exposing you to all the things you can do with the framework. Examine the code you just generated and refer to Working with RestfulX Models for more information on what you can accomplish.
If you have any questions get in touch at RestfulX Framework Google Group
For more information on the RestfulX framework refer to Home.
Want a more feature complete example? Check out Pomodo On Rails
Adding authentication and/or authorization is left as an exercise to the reader :).
OAuth
anyone?
See Soup for a simple AIR/CouchDB/RestfulX example that features synchronization and undo/redo support.