To generate a JavaScript source adapter for your RhoConnect application, you can run the rhoconnect source --js
command within your application directory.
$ rhoconnect source --help Usage: rhoconnect source [options] [args] Generates a new source adapter (Controller/Model pair). Required: name - source name (i.e. product) Options specific for this generator: --js generate JavaScript code General options: -p, --pretend Run, but do not make any changes. -f, --force Overwrite files that already exist. -s, --skip Skip files that already exist. -d, --delete Delete files that have previously been generated with this generator. --no-color Don't colorize the output -h, --help Show this message --debug Do not catch errors
For the storeserver application example, within the storeserver directory, run:
$ rhoconnect source product --js Generating with source generator: [ADDED] models/js/product.js [ADDED] controllers/js/product_controller.js
The generated source adapter’s controller file (in this case, controllers/product_controller.js
) is similar to the code listing below.
Its purpose is to define the RhoConnect application HTTP end point and to register the corresponding SYNC routes.
var app = require("ballroom"); var helpers = require("rhoconnect_helpers"); app.controllerName("Product"); app.registerHandler("sync"); // Add your custom routes here
The generated source adapter’s model file (in this case, models/product.js
) is similar to the code listing below:
var rc = require('rhoconnect_helpers'); var Product = function(){ this.login = function(resp){ // TODO: Login to your data source here if necessary resp.send(true); }; this.query = function(resp){ var result = {}; // TODO: Query your backend data source and assign the records // to a nested hash structure. Then return your result. // For example: // // { // "1": {"name": "Acme", "industry": "Electronics"}, // "2": {"name": "Best", "industry": "Software"} // } resp.send(result); }; this.create = function(resp){ // TODO: Create a new record in your backend data source. Then // return the result. resp.send('someId'); }; this.update = function(resp){ // TODO: Update an existing record in your backend data source. // Then return the result. resp.send(true); }; this.del = function(resp){ // TODO: Delete an existing record in your backend data source // if applicable. Be sure to have a hash key and value for // "object" and return the result. resp.send(true); }; this.logoff = function(resp){ // TODO: Logout from the data source if necessary. resp.send(true); }; this.storeBlob = function(resp){ // TODO: Handle post requests for blobs here. // Reference the blob object's path with resp.params.path. new rc.Exception( resp, "Please provide some code to handle blobs if you are using them." ); }; }; module.exports = new Product();
In this model you will need to implement the functions necessary for the create, query, update and delete sync operations. Optionally you may implement functions for login, logoff and storeBlob (if your application uses blobs).
You can use the following methods and techniques inside of your source adapter controller.
You can write the following methods for your source adapter model. These methods will be called by the controller at run-time and allow your source adapter model to interact with your backend service.
undefined
.The request object contains information about the HTTP request the app is receiving.
The response is used to pass application control and return the result
hash to RhoConnect.
RhoConnect provides a simple redis interface for saving/retrieving arbitrary data. This is useful if you want to save data in your application to be used later (i.e. in an async job or a subsequent source adapter execution).
Use the following exception types in your JavaScript application to handle any error conditions. Don’t worry, if your application throws an uncaught exception, the base RhoConnect::Model::Exception
will automatically be used.
RhoConnect::Model::Exception
.RhoConnect::Model::LoginException
.RhoConnect::Model::LogoffException
.RhoConnect::Model::ServerTimeoutException
.RhoConnect::Model::ServerErrorException
.RhoConnect::Model::ObjectConflictErrorException
.Here’s a complete example of how the completed product model might look:
var http = require('http'); var host = 'rhostore.herokuapp.com'; var Product = function(){ this.login = function(resp){ resp.send(true); }; this.query = function(resp){ var result = {}; var str = ''; http.request('http://' + host + '/products.json', function(res){ res.on('data', function(chunk){ str += chunk; }); res.on('end', function(){ var data = JSON.parse(str); for(var i in data){ var item = data[i]; result[item.product.id.toString()] = item.product; } resp.send(result); }); }).end(); }; this.create = function(resp){ var postData = JSON.stringify({ 'product': resp.params.create_object }); var str = ''; var options = { host: host, path: '/products.json', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length } }; var req = http.request(options, function(res){ res.on('data', function(chunk){ str += chunk; }); res.on('end', function(){ var data = JSON.parse(str); resp.send(data.product.id.toString()); }); }); req.write(postData); req.end(); }; this.update = function(resp){ var objId = resp.params.update_object.id; var putData = JSON.stringify({ "product": resp.params.update_object }); // Remove the id from the hash, we don't need it. delete putData.id; var options = { host: host, path: '/products/' + objId + '.json', method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': putData.length } }; var req = http.request(options, function(res){ res.on('data', function(){}); res.on('end', function(){ resp.send(true); }); res.on('error', function(){ resp.send(false); }); }); req.write(putData); req.end(); }; this.del = function(resp){ var objId = resp.params.delete_object.id; var options = { host: host, path: '/products/' + objId + '.json', method: 'DELETE', headers: { 'Content-Type': 'application/json' } }; var req = http.request(options, function(res){ res.on('data', function(){}); res.on('end', function(){ resp.send(true); }); res.on('error', function(){ resp.send(false); }); }); req.end(); }; this.logoff = function(resp){ resp.send(true); }; this.storeBlob = function(resp){ // TODO: Handle post requests for blobs here. // Reference the blob object's path with resp.params.path. new rc.Exception( resp, "Please provide some code to handle blobs if you are using them." ); }; }; module.exports = new Product();
If you’re doing a read-only non-authenticated source adapter, you can just write one method, query, to retrieve records as we describe here. The following is a sample query method to interact with a simple product catalog (available at http://rhostore.herokuapp.com) that exposes a REST interface. Note that RhoConnect can work with any protocol. This example shows JSON over HTTP with a REST interface, since that is common.
For a more complete example of rewriting the source adapter methods (such as create, update, and delete), refer to the source adapter example above.
Our sample web service for returning all products in the catalog (http://rhostore.herokuapp.com/products.json) returns data like this:
[ { "product": { "name": "iPhone", "brand": "Apple", "updated_at": "2010-05-11T02:04:57Z", "price": "$299.99", "quantity": "5", "id": 649, "sku": "1234", "created_at": "2010-05-11T02:04:57Z" } }, { "product": { "name": "Accord", "brand": "Honda", "updated_at": "2010-05-13T22:24:48Z", "price": "$6000", "quantity": "", "id": 648, "sku": "123", "created_at": "2010-05-11T02:04:53Z" } } ]
The JavaScript code for parsing that data, models/js/product.js
, is listed below. JSON is part of node.js by default so there is no need to explicitly include it.
You can edit models/js/product.js
within RhoStudio, or you can navigate to it within your RhoConnect application folder and edit it with a text editor.
Fill in the query method:
this.query = function(resp){ var result = {}; var str = ''; http:request('http://' + host + '/products.json', function(res){ res.on('data', function(chunk){ str += chunk; }); res.on('end', function(){ var data = JSON.parse(str); for(var i in data){ var item = data[i]; result[item.product.id.toString()] = item.product; } resp.send(result); }); }).end(); };
Each hash key in the inner hash represents an attribute of an individual object. All datatypes must be strings (so the hash values need to all be strings, not integers).
For example:
result = { "1" => { "name" => "inner tube", "brand" => "Michelin" }, "2" => { "name" => "tire", "brand" => "Michelin" } }
For your create method, the RhoConnect server will pass you a hash containing the new record, called “create_hash”. For example, it might be:
{ "name" => "Hovercraft", "brand" => "Acme" }
The RhoConnect models/js/product.js
create method will be called once for every object that has been created on the client since the last sync. Below is an example of a create method against the rhostore, which accepts an HTTP POST to perform a create action. The create method should return a unique id string for the object for it to be later modifiable by the client. If no id is returned, then you should treat the client object as read only, because it will not be able to be synchronized.
this.create = function(resp){ var postData = JSON.stringify({ 'product': resp.params.create_object }); var str = ''; var options = { host: host, path: '/products.json', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length } }; var req = http.request(options, function(res){ res.on('data', function(chunk){ str += chunk; }); res.on('end', function(){ var data = JSON.parse(str); resp.send(data.product.id.toString()); }); }); req.write(postData); req.end(); };
You will need to restart RhoConnect to reload the source adapter after modifying code. Note that the object will be created on the client right away, but it will be sent to the server on the next sync.