Application Integration

Arrow Composite Model POST

Currently in Arrow Builder, create (e.g. POST), is not supported for Composite models. However, using a pre-block we can support the POST (and UPDATE) request and implement any transaction logic required.

Take an employee model:


var Arrow = require('arrow');
var Model = Arrow.createModel('employee', {
fields: {
fname: {
type: String
},
lname: {
type: String
}
},
connector: 'appc.arrowdb',
actions: [
'create',
'read',
'update',
'delete',
'deleteAll'
]
});
module.exports = Model;

and a vacation model:


var Arrow = require('arrow');
var Model = Arrow.createModel('vacation', {
fields: {
eid: {
type: String
},
count: {
type: Number
}
},
connector: 'appc.arrowdb',
actions: [
'create',
'read',
'update',
'delete',
'deleteAll'
]
});
module.exports = Model;

and a composite model join of the two, called, employeeAndVacation:


var Arrow = require('arrow');
var Model = Arrow.createModel('employeeAndVacation', {
fields: {
fname: {
model: 'employee',
type: String
},
lname: {
model: 'employee',
type: String
},
count: {
model: 'vacation',
type: Number
}
},
connector: 'appc.composite',
actions: [
'create',
'read',
'update',
'delete',
'deleteAll'
],
metadata: {
left_join: [
{
model: 'vacation',
join_properties: {
eid: 'id'
}
}
]
}
});
module.exports = Model;

This is visually described below:

arrow-composite-1

I would like to use these models to store a list of employees and a list of vacation days for each employee. The eid field in the vacation model links to the employee model. The composite employeeAndVacation model joins the two so that I can view employee names and their vacation days together.

Currently, Arrow will return an error if I try to POST to this composite model directly to populate the fields of both models:

arrow-composite-2

Certainly, I can call the employee and vacation models separately but I would like to use Arrow and the composite employeeAndVacation model to encapsulate any transaction logic in creating and updating these models. In this example, the transaction logic is simple for a create:

  1. create an employee entry
  2. populate the new employee entry with the first and last name
  3. retrieve the id of the newly created employee
  4. create a new vacation entry
  5. populate the vacation entry with the number of vacation days for that employee and the id of the employee

‘createEmployee’ Block

I will use a pre block to help me here. The idea is to add a pre-block to the composite model that only gets executed on a POST and then it will return the proper response and block the remainder of the Arrow API flow (since it will error on a POST to a composite model for joined fields).

The first thing I need to do is define a block and run it when there is a POST (i.e. create).

I will create a pre-block, createEmployee, that implements the above transaction logic, as follows:


var Arrow = require('arrow');
var PostBlock = Arrow.Block.extend({
name: 'createEmployee',
description: 'create a new employee and linked vacation entry',
action: function (req, resp, next) {
if(!req.body.fname || !req.body.lname || !req.body.count) {
errorReply("fname AND lname AND count required, please try again");
} else {
var employee = Arrow.getModel("employee");
var employeeObject = {
fname: req.body.fname,
lname: req.body.lname
};
employee.create(employeeObject, function(err, instance){
if(err) {
errorReply("Error creating new employee");
} else {
instance.set(employeeObject);
createVacation(instance.id, req.body.count);
}
});
}
function createVacation(id, count) {
var loyalty = Arrow.getModel("vacation");
var object = {
"eid": id,
"count": count
}
loyalty.create(object, function(err, instance){
if(err) {
errorReply("Error creating new vacation");
} else {
instance.set(object);
successReply(id);
}
});
}
function errorReply(message) {
resp.response.status(500);
resp.send({"success": false,"message":message});
next(false);
}
function successReply(message) {
resp.response.status(200);
resp.send({"success": true,"message":message});
next(false);
}
}
});
module.exports = PostBlock;

Then I can modify the employeeAndPoints model to run this block before create (POST) by adding the following property:


"beforeCreate": "createEmployee",

This will cause my block, createEmployee to only run when a create (POST) is called on my composite model.

Curl Command to POST

The curl command to POST an entry in the composite model is shown below:


curl -is -X POST "https://127.0.0.1:8080/api/employeeandvacation" -d '{"fname":"AA","lname":"AA","count":"7"}' -H "Content-Type: application/json"

The curl command to GET a list of employees and their vacations via the composite model is shown below:


curl "https://127.0.0.1:8080/api/employeeandvacation"

with the following response after three POSTs:


{
"success": true,
"request-id": "a9c1af65-f258-4099-bed5-4e7b6e75c263",
"key": "employeeandvacations",
"employeeandvacations": [
{
"id": "57d36a4d1bb9b0091f81f428",
"fname": "AA",
"lname": "AA",
"count": 7
},
{
"id": "57d3697848e64b091d812bfd",
"fname": "B",
"lname": "B",
"count": 15
},
{
"id": "57d3696cb4860e09278345d6",
"fname": "A",
"lname": "A",
"count": 5
}
]
}

Summary

While Arrow’s Composite connector enables one to to easily develop complex data aggregation APIs, it does not yet provide a means for inserting any logic in between the back-end data calls. In this blog post we saw how we can create more complex API by inserting transaction logic using javascript code in a block. This provides for more fine-grained control over the API operations.