RhoConnect Source Adapters (JavaScript)

Generating the Source Adapter from the Command Line

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

Understanding the Generated Controller File

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

Understanding the Generated Model File

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).

Source Adapter API

Source Adapter Controller API

You can use the following methods and techniques inside of your source adapter controller.

  • controllerName - Name of the source adapter controller.
  • registerHandler - Register a RhoConnect handler for the controller.
  • get - Define a GET route.
  • post - Define a POST route.
  • put - Define a PUT route.
  • del - Define a DELETE route.
  • currentUser - Returns the current user who called the adapter’s model.
  • loadModel - Returns the current model instance for this controller.

Source Adapter Model API

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.

  • login - Login to your backend service.
  • logoff - Logoff from your backend service.
  • query - Query your backend service and build a hash of hashes.
  • create - Create a new record in the backend.
  • update - Update an existing record in the backend.
  • del - Delete an existing record in the backend.
  • stashResult - Saves the current state of the result to redis and sets it undefined.
  • getData - Get the model document data from Store.
  • currentUser - Returns the current user who called the adapter’s model.
  • storeBlob - Save the incoming blob data into permanent storage for the future processing.
  • partitionName - Provide a custom user partition for a model.

Request API

The request object contains information about the HTTP request the app is receiving.

  • params - Access in the incoming request parameters (i.e. from the HTTP query string).
  • header - HTTP request headers.
  • model - The corresponding model name of the request.

Response API

The response is used to pass application control and return the result hash to RhoConnect.

  • params - Access in the incoming request parameters (i.e. from the HTTP query string).
  • header - HTTP request headers.
  • send - Return program control (most controller and model methods will need to do this).
  • exception - Holds the exception for the request (if one is raised).
  • currentUser - Returns the current user who called the adapter’s model.

Store API

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).

  • getValue - Retrieve a simple value from redis.
  • putValue - Add a simple value to redis.
  • getData - Retrieve an array or hash from redis.
  • putData - Add an array or hash to redis.

Exception API

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.

Sample Model

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();

A RhoConnect Query

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"
  }
}

Creating Objects with RhoConnect

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.

Back to Top