Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

With association autoFetch cache may return incomplete instance. #685

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
16 changes: 16 additions & 0 deletions lib/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ function Model(opts) {
ExtendAssociation.extend(model, instance, opts.driver, extend_associations, assoc_opts);
};

// check all the auto-fetch associations have been populated for the given instance
var checkAssociations = function(instance) {
var checkAssn = function(associations) {
for (var assn = 0; assn < associations.length; assn++) {
if(associations[assn].autoFetch && ! instance.hasOwnProperty(associations[assn].name)) {
return false;
}
};
return true;
};
return checkAssn(one_associations) &&
checkAssn(many_associations) &&
checkAssn(extend_associations);
};

var pending = 2, create_err = null;
var instance = new Instance(model, {
uid : inst_opts.uid, // singleton unique id
Expand All @@ -112,6 +127,7 @@ function Model(opts) {
extend_associations : extend_associations,
association_properties : association_properties,
setupAssociations : setupAssociations,
checkAssociations : checkAssociations,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please rename this to hasAutoFetchAssociationsLoaded ?
It's a bit long, but more indicative of what it's actually doing.

fieldToPropertyMap : fieldToPropertyMap,
keyProperties : keyProperties
});
Expand Down
13 changes: 9 additions & 4 deletions lib/Singleton.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ exports.get = function (key, opts, createCb, returnCb) {
}
if (map.hasOwnProperty(key)) {
if (opts && opts.save_check && typeof map[key].o.saved === "function" && !map[key].o.saved()) {
// if not saved, don't return it, fetch original from db
// if not saved, don't return it, fetch original from db, but don't refresh the cache
return createCb(returnCb);
} else if (map[key].t !== null && map[key].t <= Date.now()) {
delete map[key];
} else {
return returnCb(null, map[key].o);
}
} else {
var instance = map[key].o;
if(instance.__opts.checkAssociations(instance)) {
// instance in cache has all auto-fetch associations populated - we can re-use it
return returnCb(null, instance);
}
}
}

// Can't re-use item in cache - or it's not in cache - re-fetch from db & load the cache
createCb(function (err, value) {
if (err) return returnCb(err);

Expand Down
140 changes: 140 additions & 0 deletions test/integration/association-hasone-recursive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
var ORM = require('../../');
var helper = require('../support/spec_helper');
var should = require('should');
var async = require('async');
var _ = require('lodash');

describe("hasOne", function() {
var db = null;
var Animal = null;

var setup = function (cache) {
return function (done) {
db.settings.set('instance.cache', cache);
db.settings.set('instance.returnAllErrors', true);

Animal = db.define('animal', {
id : {type : "integer", key:true},
name : {type : "text", size:"255"},
damId : {type : "integer"},
sireId : {type : "integer"}
});

Animal.hasOne('sire', Animal, {field: 'sireId', autoFetch:true});
Animal.hasOne('dam', Animal, {field: 'damId', autoFetch:false});

helper.dropSync([Animal], function(err) {
if (err) return done(err);
Animal.create([{ id:1, name: 'Bronson', sireId:10, damId:20},

{ id:10, name: 'McTavish', sireId:11, damId:12},
{ id:11, name:'Todd'},
{ id:12, name:'Jemima'},

{ id:20, name: 'Suzy', sireId:21, damId:22},
{ id:21, name:'Liam'},
{ id:22, name:'Glencora'}
], done);
});
};
};

before(function(done) {
helper.connect(function (connection) {
db = connection;
done();
});
});

[false, 1].forEach(function(cache) {

describe("recursive hasOne() with " + (cache ? "" : "out ") + "cache", function () {
before(setup(cache));

it("should get Bronson's Sire but not Dam", function (done) {
Animal.find({name: "Bronson"}, function(err, animals) {
should.not.exist(err);

animals[0].name.should.equal("Bronson");

// All of Bronson's Sire & some of his Dam stuff should be present
animals[0].sireId.should.equal(10);
animals[0].damId.should.equal(20);

animals[0].should.have.property("sire");
animals[0].should.not.have.property("dam"); // no auto-fetch

animals[0].sire.name.should.equal("McTavish");

// Bronson's paternal GrandSire & GrandDam shouldn't be present - autoFetchLimit
animals[0].sire.should.not.have.property("sire");
animals[0].sire.should.not.have.property("dam");

// but Bronson's paternal GrandSire & GrandDam ID's should be known
animals[0].sire.sireId.should.equal(11);
animals[0].sire.damId.should.equal(12);

return done();
});
});

it("should get McTavish's Sire but not Dam", function (done) {
Animal.find({name: "McTavish"}, function(err, animals) {
should.not.exist(err);

animals[0].name.should.equal("McTavish");

// All of McTavish's Sire & some of his Dam stuff should be present
animals[0].sireId.should.equal(11);
animals[0].damId.should.equal(12);

animals[0].should.have.property("sire"); // now fixed by stueynz
animals[0].should.not.have.property("dam"); // no auto-fetch

animals[0].sire.name.should.equal("Todd"); // just to be sure

// Let's make sure the cache is still working...
Animal.get(animals[0].id, function(err, McTavish) {
should.not.exist(err);

if(cache) {
McTavish.should.equal(animals[0]);
} else {
McTavish.should.not.equal(animals[0]);
}
return done();
});
});
});

it("should get Suzy's Sire but not Dam", function (done) {
Animal.find({name: "Suzy"}, function(err, animals) {
should.not.exist(err);

animals[0].name.should.equal("Suzy");

// All of Suzy's Sire & Dam stuff should be present
animals[0].sireId.should.equal(21);
animals[0].damId.should.equal(22);

animals[0].should.have.property("sire") // now fixed by stueynz
animals[0].should.not.have.property("dam"); // no auto-fetch

animals[0].sire.name.should.equal("Liam"); // just to be sure

// Let's make sure the cache is still working...
Animal.get(animals[0].id, function(err, Suzy) {
should.not.exist(err);

if(cache) {
Suzy.should.equal(animals[0]);
} else {
Suzy.should.not.equal(animals[0]);
}
return done();
});
});
});
});
});
});