Skip to content

Commit f600366

Browse files
committed
Fixes stack overflow on instance.save() with a reversed hasOne association (#338)
1 parent 2c98237 commit f600366

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

lib/Instance.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function Instance(Model, opts) {
1212
opts.changes = (opts.is_new ? Object.keys(opts.data) : []);
1313
opts.extrachanges = [];
1414

15+
var instance_saving = false;
1516
var events = {};
1617
var instance = {};
1718
var emitEvent = function () {
@@ -86,13 +87,26 @@ function Instance(Model, opts) {
8687
});
8788
};
8889
var saveError = function (cb, err) {
90+
instance_saving = false;
91+
8992
emitEvent("save", err, instance);
93+
9094
Hook.trigger(instance, opts.hooks.afterSave, false);
95+
9196
if (typeof cb === "function") {
9297
cb(err, instance);
9398
}
9499
};
95100
var saveInstance = function (cb, saveOptions) {
101+
// what this condition means:
102+
// - If the instance is in state mode
103+
// - AND it's not an association that is asking it to save
104+
// -> return has already saved
105+
if (instance_saving && !(saveOptions.saveAssociations === false)) {
106+
return cb(null, instance);
107+
}
108+
instance_saving = true;
109+
96110
handleValidations(function (err) {
97111
if (err) {
98112
return saveError(cb, err);
@@ -127,6 +141,8 @@ function Instance(Model, opts) {
127141
});
128142
};
129143
var afterSave = function (cb, create, err) {
144+
instance_saving = false;
145+
130146
emitEvent("save", err, instance);
131147

132148
if (create) {
@@ -498,9 +514,10 @@ function Instance(Model, opts) {
498514

499515
while (arguments.length > 0) {
500516
arg = Array.prototype.shift.call(arguments);
501-
switch(typeof arg) {
517+
518+
switch (typeof arg) {
502519
case 'object':
503-
switch(objCount) {
520+
switch (objCount) {
504521
case 0:
505522
data = arg;
506523
break;

test/integration/instance.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,52 @@ describe("Model instance", function() {
5050
return db.close();
5151
});
5252

53+
describe("#save", function () {
54+
var main_item, item;
55+
56+
before(function (done) {
57+
main_item = db.define("main_item", {
58+
name : String
59+
}, {
60+
auteFetch : true
61+
});
62+
item = db.define("item", {
63+
name : String
64+
}, {
65+
cache : false
66+
});
67+
item.hasOne("main_item", main_item, {
68+
reverse : "items",
69+
autoFetch : true
70+
});
71+
72+
return helper.dropSync([ main_item, item ], function () {
73+
main_item.create({
74+
name : "Main Item"
75+
}, function (err, mainItem) {
76+
item.create({
77+
name : "Item"
78+
}, function (err, Item) {
79+
mainItem.setItems(Item, function (err) {
80+
should.not.exist(err);
81+
82+
return done();
83+
});
84+
});
85+
});
86+
});
87+
});
88+
89+
it("should have a saving state to avoid loops", function (done) {
90+
main_item.find({ name : "Main Item" }).first(function (err, mainItem) {
91+
mainItem.save({ name : "new name" }, function (err) {
92+
should.not.exist(err);
93+
return done();
94+
});
95+
});
96+
});
97+
});
98+
5399
describe("#isInstance", function () {
54100
it("should always return true for instances", function (done) {
55101
should.equal((new Person).isInstance, true);

0 commit comments

Comments
 (0)