Skip to content

Commit 41f0c41

Browse files
author
robin
committed
Negative Ids by default. Ref T32139
1 parent cec7709 commit 41f0c41

File tree

7 files changed

+1113
-58
lines changed

7 files changed

+1113
-58
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { Model } from 'mobx-spine';
4545

4646
// Define a class Animal, with 2 observed properties `id` and `name`.
4747
class Animal extends Model {
48-
@observable id = null; // Default value is null.
48+
@observable id; // Default value is undefined, a new negative integer will automatically be generated for a new model.
4949
@observable name = ''; // Default value is ''.
5050
}
5151
```
@@ -56,7 +56,7 @@ If we instantiate a new animal without arguments it will create an empty animal
5656
// Create an empty instance of an Animal.
5757
const lion = new Animal();
5858

59-
console.log(lion.id); // null
59+
console.log(lion.id); // -1, a unique negative integer for this model
6060
console.log(lion.name); // ''
6161
```
6262

@@ -67,6 +67,7 @@ You can also supply data when creating a new instance:
6767
const cat = new Animal({ id: 1, name: 'Cat' });
6868

6969
console.log(cat.name); // Cat
70+
console.log(cat.id); // 1
7071
```
7172

7273
When data is supplied in the constructor, these can be reset by calling `clear`:
@@ -90,6 +91,13 @@ cat.name = '';
9091
console.log(cat.undefinedProperty); // undefined
9192
```
9293

94+
### New models
95+
A new model is a model that exists in the store, but not on the backend. A new model either has a negative id, or the id is `null`. Checking if a model is new can be done with `model.isNew()` which returns a boolean `true` when the model is new.
96+
97+
By default, a new model will be initialized with a negative id, this way the model can be used as a related model. When a model is initialized with a negative id it will automatically generate a new negative id for the model on a clear. In some cases a `null` id might be preferred, in this case a model can be forced to get a `null` id by passing `{id: null}` in the constructor. In this case the id will also be reset to `null` on a clear. A model with a `null` id functions the same as a model with a negative id other than that the model cannot be used in a relation.
98+
99+
Some projects might still use the legacy method of checking for new models by checking if `!model.id`. This does not work with the default negative IDs. To migrate a project to the default negative IDs implementation you should search and replace the whole project for occurrences of `.id` and fix all lines that check if an id is set to use the `isNew()` property.
100+
93101
### Constructor: options
94102

95103
|key|default| | |
@@ -308,6 +316,9 @@ class animal = new Animal({ id: 2, name: 'Rova', breed: { id: 3, name: 'Main Coo
308316
console.log(animal.breed.name); // Throws cannot read property name from undefined.
309317
```
310318

319+
### Negative IDs for related models
320+
A related model will always be initialized with a `null` id. When the id of the related model is set to `null` it indicates to django-binder that the field is empty.
321+
311322
### Pick fields
312323

313324
You can pick fields by either defining a static `pickFields` variable or a `pickFields` function. Keep in mind that `id` is mandatory, so it will always be included.

dist/mobx-spine.cjs.js

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -907,14 +907,40 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
907907
value: function getNegativeId() {
908908
return -parseInt(this.cid.replace('m', ''));
909909
}
910+
911+
/**
912+
* Get InternalId returns the id of a model or a negative id if the id is not set
913+
* @returns {*} the id of a model or a negative id if the id is not set
914+
*/
915+
910916
}, {
911917
key: 'getInternalId',
912918
value: function getInternalId() {
913-
if (this.isNew) {
919+
if (!this[this.constructor.primaryKey]) {
914920
return this.getNegativeId();
915921
}
916922
return this[this.constructor.primaryKey];
917923
}
924+
925+
/**
926+
* Gives the model the internal id, meaning that it will keep the set id of the model or will receive a negative
927+
* id if the id is null. This is useful if you have a new model that you want to give an id so that it can be
928+
* referred to in a relation.
929+
*/
930+
931+
}, {
932+
key: 'assignInternalId',
933+
value: function assignInternalId() {
934+
this[this.constructor.primaryKey] = this.getInternalId();
935+
}
936+
937+
/**
938+
* The get url returns the url for a model., it appends the id if there is one. If the model is new it should not
939+
* append an id.
940+
*
941+
* @returns {string} the url for a model
942+
*/
943+
918944
}, {
919945
key: 'casts',
920946
value: function casts() {
@@ -945,12 +971,18 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
945971
key: 'url',
946972
get: function get$$1() {
947973
var id = this[this.constructor.primaryKey];
948-
return '' + lodash.result(this, 'urlRoot') + (id ? id + '/' : '');
974+
return '' + lodash.result(this, 'urlRoot') + (!this.isNew ? id + '/' : '');
949975
}
976+
977+
/**
978+
* A model is considered new if it does not have an id, or if the id is a negative integer.
979+
* @returns {boolean} True if the model id is not set or a negative integer
980+
*/
981+
950982
}, {
951983
key: 'isNew',
952984
get: function get$$1() {
953-
return !this[this.constructor.primaryKey];
985+
return !this[this.constructor.primaryKey] || this[this.constructor.primaryKey] < 0;
954986
}
955987
}, {
956988
key: 'isLoading',
@@ -1016,6 +1048,16 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
10161048
if (options.relations) {
10171049
this.__parseRelations(options.relations);
10181050
}
1051+
1052+
// The model will automatically be assigned a negative id, the id will still be overridden if it is supplied in the data
1053+
this.assignInternalId();
1054+
1055+
// We want our id to remain negative on a clear, only if it was not created with the id set to null
1056+
// which is usually the case when the object is a related model in which case we want the id to be reset to null
1057+
if (data && data[this.constructor.primaryKey] !== null || !data) {
1058+
this.__originalAttributes[this.constructor.primaryKey] = this[this.constructor.primaryKey];
1059+
}
1060+
10191061
if (data) {
10201062
this.parse(data);
10211063
}
@@ -1061,7 +1103,8 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
10611103
if (RelModel.prototype instanceof Store) {
10621104
return new RelModel(options);
10631105
}
1064-
return new RelModel(null, options);
1106+
// If we have a related model, we want to force the related model to have id null as that means there is no model set
1107+
return new RelModel(defineProperty({}, RelModel.primaryKey, null), options);
10651108
}));
10661109
}
10671110

@@ -1090,15 +1133,15 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
10901133
value: function toBackend() {
10911134
var _this4 = this;
10921135

1093-
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1136+
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
10941137

1095-
var _ref$data = _ref.data,
1096-
data = _ref$data === undefined ? {} : _ref$data,
1097-
_ref$mapData = _ref.mapData,
1098-
mapData = _ref$mapData === undefined ? function (x) {
1138+
var _ref2$data = _ref2.data,
1139+
data = _ref2$data === undefined ? {} : _ref2$data,
1140+
_ref2$mapData = _ref2.mapData,
1141+
mapData = _ref2$mapData === undefined ? function (x) {
10991142
return x;
1100-
} : _ref$mapData,
1101-
options = objectWithoutProperties(_ref, ['data', 'mapData']);
1143+
} : _ref2$mapData,
1144+
options = objectWithoutProperties(_ref2, ['data', 'mapData']);
11021145

11031146
var output = {};
11041147
// By default we'll include all fields (attributes+relations), but sometimes you might want to specify the fields to be included.
@@ -1278,12 +1321,14 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
12781321
key: '__scopeBackendResponse',
12791322
value: function __scopeBackendResponse(_ref2) {
12801323
var _this7 = this;
1324+
value: function __scopeBackendResponse(_ref3) {
1325+
var _this8 = this;
12811326

1282-
var data = _ref2.data,
1283-
targetRelName = _ref2.targetRelName,
1284-
repos = _ref2.repos,
1285-
mapping = _ref2.mapping,
1286-
reverseMapping = _ref2.reverseMapping;
1327+
var data = _ref3.data,
1328+
targetRelName = _ref3.targetRelName,
1329+
repos = _ref3.repos,
1330+
mapping = _ref3.mapping,
1331+
reverseMapping = _ref3.reverseMapping;
12871332

12881333
var scopedData = null;
12891334
var relevant = false;
@@ -1351,11 +1396,13 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
13511396
key: 'fromBackend',
13521397
value: function fromBackend(_ref3) {
13531398
var _this8 = this;
1399+
value: function fromBackend(_ref4) {
1400+
var _this9 = this;
13541401

1355-
var data = _ref3.data,
1356-
repos = _ref3.repos,
1357-
relMapping = _ref3.relMapping,
1358-
reverseRelMapping = _ref3.reverseRelMapping;
1402+
var data = _ref4.data,
1403+
repos = _ref4.repos,
1404+
relMapping = _ref4.relMapping,
1405+
reverseRelMapping = _ref4.reverseRelMapping;
13591406

13601407
// We handle the fromBackend recursively. On each relation of the source model
13611408
// fromBackend gets called as well, but with data scoped for itself
@@ -1777,6 +1824,13 @@ var Model = (_class$1 = (_temp$1 = _class2$1 = function () {
17771824
var _this18 = this;
17781825

17791826
lodash.forIn(this.__originalAttributes, function (value, key) {
1827+
// If it is our primary key, and the primary key is negative, we generate a new negative pk, else we set it
1828+
// to the value
1829+
if (key === _this19.constructor.primaryKey && value < 0) {
1830+
_this19[key] = -1 * lodash.uniqueId();
1831+
} else {
1832+
_this19[key] = value;
1833+
}
17801834
_this18[key] = value;
17811835
});
17821836

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"./src"
7474
],
7575
"testPathIgnorePatterns": [
76-
"/fixtures/"
76+
"/fixtures/",
77+
"/helpers.js"
7778
]
7879
}
7980
}

0 commit comments

Comments
 (0)