Skip to content

Commit e9adf16

Browse files
buildymcbuildykobaska
authored andcommitted
Allow custom properties to Change Model
Allow custom properties to Change Model, and make change filter customizable through mixins to allow to add the cutom property to the filter. fix failing test
1 parent 68d55b5 commit e9adf16

File tree

4 files changed

+301
-22
lines changed

4 files changed

+301
-22
lines changed

common/models/change.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,32 @@ module.exports = function(Change) {
186186

187187
cb = cb || utils.createPromiseCallback();
188188

189-
change.currentRevision(function(err, rev) {
189+
var model = this.getModelCtor();
190+
var id = this.getModelId();
191+
192+
model.findById(id, function(err, inst) {
190193
if (err) return cb(err);
191194

195+
if (inst) {
196+
inst.fillCustomChangeProperties(change, function() {
197+
var rev = Change.revisionForInst(inst);
198+
prepareAndDoRectify(rev);
199+
});
200+
} else {
201+
prepareAndDoRectify(null);
202+
}
203+
});
204+
205+
return cb.promise;
206+
207+
function prepareAndDoRectify(rev) {
192208
// avoid setting rev and prev to the same value
193209
if (currentRev === rev) {
194210
change.debug('rev and prev are equal (not updating anything)');
195211
return cb(null, change);
196212
}
197213

198-
// FIXME(@bajtos) Allo callers to pass in the checkpoint value
214+
// FIXME(@bajtos) Allow callers to pass in the checkpoint value
199215
// (or even better - a memoized async function to get the cp value)
200216
// That will enable `rectifyAll` to cache the checkpoint value
201217
change.constructor.getCheckpointModel().current(
@@ -204,8 +220,7 @@ module.exports = function(Change) {
204220
doRectify(checkpoint, rev);
205221
}
206222
);
207-
});
208-
return cb.promise;
223+
}
209224

210225
function doRectify(checkpoint, rev) {
211226
if (rev) {
@@ -230,7 +245,7 @@ module.exports = function(Change) {
230245
if (currentRev) {
231246
change.prev = currentRev;
232247
} else if (!change.prev) {
233-
change.debug('ERROR - could not determing prev');
248+
change.debug('ERROR - could not determine prev');
234249
change.prev = Change.UNKNOWN;
235250
}
236251
change.debug('updated prev');

lib/persisted-model.js

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,17 +1064,16 @@ module.exports = function(registry) {
10641064
var idName = this.dataSource.idName(this.modelName);
10651065
var Change = this.getChangeModel();
10661066
var model = this;
1067+
var changeFilter = this.createChangeFilter(since, filter);
10671068

10681069
filter = filter || {};
10691070
filter.fields = {};
10701071
filter.where = filter.where || {};
10711072
filter.fields[idName] = true;
10721073

10731074
// TODO(ritch) this whole thing could be optimized a bit more
1074-
Change.find({ where: {
1075-
checkpoint: { gte: since },
1076-
modelName: this.modelName
1077-
}}, function(err, changes) {
1075+
1076+
Change.find(changeFilter, function(err, changes) {
10781077
if (err) return callback(err);
10791078
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
10801079
var ids = changes.map(function(change) {
@@ -1738,11 +1737,15 @@ module.exports = function(registry) {
17381737
assert(BaseChangeModel,
17391738
'Change model must be defined before enabling change replication');
17401739

1740+
var additionalChangeModelProperties = {};
1741+
1742+
if (this.settings.additionalChangeModelProperties) {
1743+
additionalChangeModelProperties = this.settings.additionalChangeModelProperties;
1744+
}
1745+
17411746
this.Change = BaseChangeModel.extend(this.modelName + '-change',
1742-
{},
1743-
{
1744-
trackModel: this
1745-
}
1747+
additionalChangeModelProperties,
1748+
{trackModel: this}
17461749
);
17471750

17481751
if (this.dataSource) {
@@ -1763,7 +1766,6 @@ module.exports = function(registry) {
17631766
self.Change.getCheckpointModel().attachTo(self.dataSource);
17641767
}
17651768
};
1766-
17671769
PersistedModel.rectifyAllChanges = function(callback) {
17681770
this.getChangeModel().rectifyAll(callback);
17691771
};
@@ -1908,6 +1910,80 @@ module.exports = function(registry) {
19081910
}
19091911
};
19101912

1913+
/**
1914+
* Get the filter for searching related changes. Override this function, if need to use properties from the model
1915+
* filter as part the change search filter
1916+
*
1917+
* example override for this function(in a mixin) when a custom property is used in change model, using model instance
1918+
* property value in the change filter.
1919+
*
1920+
* module.exports = (TargetModel, config) => {
1921+
* var originalFilterFactory = TargetModel.createChangeFilter;
1922+
*
1923+
* TargetModel.createChangeFilter = function (since, modelFilter) {
1924+
* var customProperty = TargetModel.settings.changeCustomProperty;
1925+
*
1926+
* var filter = originalFilterFactory.call(this, since, modelFilter);
1927+
* if (modelFilter && modelFilter.where && modelFilter.where[customProperty]) {
1928+
* filter.where[customProperty] = modelFilter.where[customProperty];
1929+
* }
1930+
* return filter;
1931+
* };
1932+
* };
1933+
*
1934+
* @param {Number} since Return only changes since this checkpoint.
1935+
* @param {Object} modelFilter filter used for the model
1936+
* @returns {Object} The filter object to pass to `Change.find()`, typically `{{where: {checkpoint: {gte: *}, modelName: *}}}`
1937+
*/
1938+
PersistedModel.createChangeFilter = function(since, modelFilter) {
1939+
return {
1940+
where: {
1941+
checkpoint: {gte: since},
1942+
modelName: this.modelName
1943+
}
1944+
};
1945+
};
1946+
1947+
/**
1948+
* Add custom change model property data.
1949+
* Override this function if custom properties exist in your change model which need to be filled during change creation
1950+
*
1951+
* example override for this function(in a mixin) to fill custom properties during change model creation
1952+
*
1953+
* module.exports = (TargetModel, config) => {
1954+
* var originalCustomChangeDataFactory = TargetModel.prototype.fillCustomChangeProperties;
1955+
*
1956+
* TargetModel.prototype.fillCustomChangeProperties = function (change, cb) {
1957+
* var changeCustomPropertyValue = null;
1958+
* var changeCustomProperty;
1959+
* var inst = this;
1960+
*
1961+
* originalCustomChangeDataFactory.call(this, change, function (err) {
1962+
* if (err) return cb(err);
1963+
* if (TargetModel && TargetModel.settings && TargetModel.settings.changeCustomProperty) {
1964+
* changeCustomProperty = TargetModel.settings.changeCustomProperty;
1965+
* }
1966+
*
1967+
* if (changeCustomProperty && inst && inst[changeCustomProperty]) {
1968+
* changeCustomPropertyValue = inst[changeCustomProperty];
1969+
* }
1970+
*
1971+
* change[changeCustomProperty] = changeCustomPropertyValue;
1972+
*
1973+
* cb(null);
1974+
* });
1975+
* };
1976+
* };
1977+
*
1978+
*
1979+
* @callback {Function} callback
1980+
* @param change
1981+
*/
1982+
PersistedModel.prototype.fillCustomChangeProperties = function(change, cb) {
1983+
// no-op by default
1984+
cb(null);
1985+
};
1986+
19111987
PersistedModel.setup();
19121988

19131989
return PersistedModel;

test/change.test.js

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
var async = require('async');
77
var expect = require('chai').expect;
88

9-
var Change;
10-
var TestModel;
11-
129
describe('Change', function() {
10+
var Change;
11+
var TestModel;
12+
1313
beforeEach(function() {
1414
var memory = loopback.createDataSource({
1515
connector: loopback.Memory
@@ -320,7 +320,6 @@ describe('Change', function() {
320320
change.rectify()
321321
.then(function(ch) {
322322
assert.equal(ch.rev, test.revisionForModel);
323-
324323
done();
325324
})
326325
.catch(done);
@@ -608,3 +607,73 @@ describe('Change', function() {
608607
});
609608
});
610609
});
610+
611+
describe('Change with with custom properties', function() {
612+
var ChangeWithCustomProperty;
613+
var TestModelWithModifiedChangeModel;
614+
var customProperty = 'tenantId';
615+
616+
beforeEach(function() {
617+
var memory = loopback.createDataSource({
618+
connector: loopback.Memory
619+
});
620+
621+
var additionalChangeModelProperties = {};
622+
additionalChangeModelProperties[customProperty] = {type: 'string'};
623+
624+
TestModelWithModifiedChangeModel = loopback.PersistedModel
625+
.extend('ChangeTestModel',
626+
{
627+
id: {id: true, type: 'string', defaultFn: 'guid'}
628+
},
629+
{
630+
trackChanges: true,
631+
additionalChangeModelProperties: additionalChangeModelProperties
632+
});
633+
this.modelName = TestModelWithModifiedChangeModel.modelName;
634+
635+
TestModelWithModifiedChangeModel.prototype.fillCustomChangeProperties = function(change, cb) {
636+
var customPropertyValue = null;
637+
var inst = this;
638+
639+
if (inst && inst[customProperty]) {
640+
customPropertyValue = inst[customProperty];
641+
}
642+
change[customProperty] = customPropertyValue;
643+
644+
cb(null);
645+
};
646+
647+
TestModelWithModifiedChangeModel.attachTo(memory);
648+
TestModelWithModifiedChangeModel._defineChangeModel();
649+
ChangeWithCustomProperty = TestModelWithModifiedChangeModel.getChangeModel();
650+
});
651+
652+
describe('change.rectify', function() {
653+
var change;
654+
var customPropertyValue = '123';
655+
656+
beforeEach(function() {
657+
var test = this;
658+
659+
test.data = {
660+
foo: 'bar'
661+
};
662+
test.data[customProperty] = customPropertyValue;
663+
664+
return TestModelWithModifiedChangeModel.create(test.data)
665+
.then(function(model) {
666+
return ChangeWithCustomProperty.findOrCreateChange(TestModelWithModifiedChangeModel.modelName, model.id);
667+
}).then(function(ch) {
668+
change = ch;
669+
});
670+
});
671+
672+
it('creates a new change with the custom property', function() {
673+
return change.rectify()
674+
.then(function(ch) {
675+
assert.equal(ch[customProperty], customPropertyValue);
676+
});
677+
});
678+
});
679+
});

0 commit comments

Comments
 (0)