Very hard question to articulate but here goes. Ultimately I have a set of models in RethinkDB that are related to each other in a sort of chain pattern. I am experiencing a problem when I want to use a field defined with a "hasMany" relationship, using the Thinky ORM, to place an array of rethinkdb models into to save to the database and create the relationships with the objects.

Since this is very difficult and lengthy to explain in paragraphs, I have created an isolated test case to illustrate, in code, the problem I am facing.

var Reqlite = require('reqlite');
var assert = require('assert');

var thinky = require('thinky')({
    "host": "localhost",
    "port": 28016,
    "db": "test"
});

var server = new Reqlite({"driver-port": 28016});

var r = thinky.r;
var type = thinky.type;


// Models
var Account = thinky.createModel('accounts', {
    id: type.string(),
    username: type.string(),
});

var User = thinky.createModel('users', {
    id: type.string(),
    name: type.string(),
    accountId: type.string(),
    archetypeId: type.string(),
});

var Archetype = thinky.createModel('archetypes', {
    id: type.string(),
    name: type.string(),
});

var Node = thinky.createModel('nodes', {
    id: type.string(),
    skillId: type.string(),
    archetypeId: type.string(),
});

var Skill = thinky.createModel('skills', {
    id: type.string(),
    name: type.string(),
    desc: type.string(),
});


// Relationships
// Account <--> User
Account.hasMany(User, 'users', 'id', 'accountId');
User.belongsTo(Account, 'owner', 'accountId', 'id');

// User <--> Archetype
User.belongsTo(Archetype, 'archetype', 'archetypeId', 'id');

// Archetype <--> Node
Archetype.hasMany(Node, 'nodes', 'id', 'archetypeId');
Node.belongsTo(Archetype, 'archetype', 'archetypeId', 'id');

// Node <--> Skill
Node.belongsTo(Skill, 'skill', 'skillId', 'id');
Skill.hasMany(Node, 'nodes', 'id', 'skillId');


before(function(done) {

    var skillDebugging = Skill({
        id: '100',
        name: 'Debugging',
        desc: 'Increase knowledge of the inner working of things',
    });
    var skillBuilding = Skill({
        id: '110',
        name: 'Building',
        desc: 'Survive the Apocalypse with this',
    });

    var debuggerNode = Node({
        id: '200',
        skill: skillDebugging,
    });

    var builderNode = Node({
        id: '210',
        skill: skillBuilding,
    });

    var jackBuildNode = Node({
        id: '220',
        skill: skillBuilding,
    });
    var jackDebugNode = Node({
        id: '230',
        skill: skillDebugging,
    });

    var archetype1 = Archetype({
        id: '300',
        name: 'Debugger',
        nodes: [debuggerNode],
    });
    var archetype2 = Archetype({
        id: '310',
        name: 'Builder',
        nodes: [builderNode],
    });
    var archetype3 = Archetype({
        id: '320',
        name: 'Jack-O-Trades',
        nodes: [jackBuildNode, jackDebugNode],
    });

    archetype1.saveAll().then(function(result) {
        archetype2.saveAll().then(function(result1) {
            archetype3.saveAll().then(function(result2) {
                done();
            }).error(done).catch(done);
        }).error(done).catch(done);
    }).error(done).catch(done);
});

describe('New Test', function() {
    it('should return a list of archetypes with all joined nodes and skills', function(done) {
        Archetype.getJoin().then(function(archetypes) {
            // here we should expect to see the data saved and joined in each archetype as it was defined
            // above in the "before" block
            console.info('\nList of archetypes: ', JSON.stringify(archetypes));
            assert.ok(archetypes);

            archetypes.forEach(function(archetype) {
                assert.ok(archetype);
                assert.ok(archetype.nodes);
                archetype.nodes.forEach(function(node) {
                    assert.ok(node);
                    assert.ok(node.skill);
                });
            });
            done();
        }).error(done).catch(done);
    });

    it('should return a skill with a list of nodes joined', function(done) {
        Skill.get('100').getJoin().then(function(debugSkill) {
            console.info('\nDebug skill JSON: ', JSON.stringify(debugSkill));
            // if successful, we should see two nodes associated with this skill
            assert.ok(debugSkill);
            assert.ok(debugSkill.nodes);
            assert.ok(debugSkill.nodes.length > 1);
            done();
        }).error(done).catch(done);
    });
});

This is a test case using Reqlite to simulate a RethinkDB database, thinky as the ORM, and mocha as the test suite to run the tests. In the code example, the last archetype, id: '320' and name Jack-O-Trades, is the one that experiences the problem, where the other two build out fine. The problem is, only the jackBuildNode is saved with its proper association with the skill model. The second node, jackDebugNode in the nodes array on Jack-O-Trades is saved and created in the database, but does not save the relationship that node should have with the skillDebugging skill model.

Can anyone see where I might be going wrong in the code and/or logic here? I do understand that there are ways that I could get around this by modularizing the saves and just update relationships afterwords, but it would be much more safe to have the single saveAll() operation be responsible for associating and creating this data since the single operation is more likely to succeed without corruption where as breaking it into multiple save/update calls would create a potential for incomplete saves/updates. Any insight would be greatly appreciated.

1

There are 1 answers

0
don_vito On BEST ANSWER

After posting this question as an issue on the thinky github, I received a solution from the author which is described in the issue. For those that may be searching for the answer here, the summary of the problem is this:

The thinky .saveAll() method used without parameters passed is described as deprecated by the author and should always specify what relations you want to save in order to get the .saveAll() method to recurse properly. Check out the thinky docs on the parameters passed in .saveAll() and also check out the modified code sample posted by neumino to see how this code sample was slightly modified to insert the saveAll parameter and get the proper relations saved.