Search code examples
node.jsnode-orm2

Node.js orm2 - trouble retrieving new associations in OneToMany relationship


I'm playing with node-orm2 to get a feel for what it's like to use in a RESTful API. I'm using a Postgres database on the backend.

I'm trying a variation on their docs where I have a OneToMany relationship between a Person and a Task with the following in a single file:

app.js

'strict'

// Based on http://blog.modulus.io/nodejs-and-express-create-rest-api
// but with my own twists!

var express = require('express');
var app = express();
var orm = require('orm');

// Helpers
var collectionize = function(target){
    if(typeof target == 'Object'){
        return [target];
    }
    return target;
};

// Configure the app with any middleware
app.use(express.bodyParser());
app.use(orm.express("postgres://rodrigomartell:password@localhost/postgres",{
    define : function(db, models, next){

        // Define models
        models.Person = db.define("Person",
            {
                name     : String,
                surname  : String,
                age      : Number,
                male     : Boolean,
                continent: ["enum1", "enum2"],
                data     : Object
            },
            {
                methods : {
                    fullName: function(){
                        return this.name  + this.surname;
                    }
                },
                validations : {
                    name: orm.enforce.unique("name already taken!"),
                    age : orm.enforce.ranges.number(18, undefined, "under-age")
                },
                autoFetch : true // global eager load
            }
        );
        models.Task = db.define("Task",
            {
                description: String
            }
        );

        // Relations
        models.Task.hasOne('person', models.Person, {reverse:'tasks', required: true});

        // Finally drop and sync this puppy
        db.drop(function(){
            db.sync(function(){
                console.log('All good');
                // Create one Person on load
                db.models.Person.create([{
                    name     : "Kenny",
                    surname  : "Powers",
                    age      : 34,
                    male     : true,
                    continent: "enum1"
                }], function(err, items){
                    var person = items[0];
                    db.models.Task.create([{description:'Heyo!', person_id: person.id}], function(err, tasks){
                        console.log(err,tasks);
                    });
                });
                // Press on?
                next(); // After synching, get on with the rest of the app?
            },function(err){
                console.log('No good', err);
            });
        });
    }
}));


// Configure the routes and that
app.get("/people", function(req,res){
    console.log('requested');
    res.type('text/plain');
    // req.models is a reference to models in the middleware config step
    req.models.Person.find(null, function(err, people){ // There must be a neater way to findAll?
        res.json(people);
    });
});

app.post("/people", function(req,res){
    console.log('requested');
    res.type('text/plain');
    req.models.Person.create(collectionize(req.body), function(err, newPerson){
        if(err){
            res.json({error: err});
        }
        else{
            res.json(newPerson);
        }
    });
});

app.get("/tasks", function(req, res){
    res.type('text/plain');
    req.models.Task.find(null, function(err, tasks){ // There must be a neater way to findAll?
        res.json(tasks);
    });
});

app.post("/tasks", function(req, res){
    res.type('text/plain');
    var task = req.body;
    req.models.Task.create([req.body], function(err, task){
        if(err){
            res.json(err);
        }
        else{
            res.json(task);
        }
    });
});


// Listen up!
app.listen(4730, function(){
    console.log('Listening on http://localhost:4730');
});

I've set GET and POST routes for the following resources:

  • {GET,POST} /people

  • {GET,POST} /tasks

On initial load of the DB I create a Person instance with a task just to have something in the DB.

I can do a GET on localhost:4730/people and get back the person created at load time with its task (awesome):

[{
    name     : "Kenny",
    surname  : "Powers",
    age      : 34,
    male     : true,
    continent: "enum1",
    data     : null,
    id       : 1,
    tasks    : [
        {
            description: "Heyo!",
            id: 1,
            person_id: 1
        }
    ]
}]

If I do a GET on localhost:4730/tasks I get back as expected:

[
    {
        description: "Heyo!",
        id: 1,
        person_id: 1
    }
]

Now, here's the start of my problem:

If I do a POST to localhost:4730/tasks with the payload:

{
    description: "Another task",
    person_id: 1
}

And then do a fresh GET on localhost:4730/tasks I get this as expected:

[{
    description: "Heyo!",
    id: 1,
    person_id: 1
},
{
    description: "Another task",
    id: 2,
    person_id: 1
}]

Now, doing a fresh GET on localhost:4730/people one would expect to show two tasks assigned to person_id 1 (Kenny Powers), but alas, the association seems to have registered on the many side but not in the one side of the relationship:

   [{
        name     : "Kenny",
        surname  : "Powers",
        age      : 34,
        male     : true,
        continent: "enum1",
        data     : null,
        id       : 1,
        tasks    : [
            {
                description: "Heyo!",
                id: 1,
                person_id: 1
            }
        ]
    }]

I can't work out from the docs where I may be going wrong. Has anyone experienced similar issues?

Thanks.


Solution

  • I was just checking in on this stale question and saw that I got a tumbleweed badge for it. Not sure if that's good or bad (will google it in a minute), but I'll take it as good.

    Anyway, I just played with this some more and found this post, which led me to try turning cache off (true is default) in the Person model.

    So, it all works if in the Person model one adds a new property after autoFetch to set cache to false. It should look like this:

        models.Person = db.define("Person",
            {
                name     : String,
                surname  : String,
                age      : Number,
                male     : Boolean,
                continent: ["enum1", "enum2"],
                data     : Object
            },
            {
                methods : {
                    fullName: function(){
                        return this.name  + this.surname;
                    }
                },
                validations : {
                    name: orm.enforce.unique("name already taken!"),
                    age : orm.enforce.ranges.number(18, undefined, "under-age")
                },
                autoFetch : true, // global eager load
                cache : false // Important as otherwise it doesn't update the relationship, why?
            }
        );
    

    I can't say I fully understand why that works and will dig into it to the best of my ability, but glad it's fixed.

    I hope it helps anyone landing here with a similar problem. I'm off to show off my new tumbleweed badge to my imaginary friends in an empty desert scene now...