TLDR: Loopback uses the default for a field when a document is created with that field missing.
But wouldn't it be useful to also fill out that field when reading and writing documents?
(E.g. if a new field is added, but documents in the DB do not have a value for that field.)
If not, then what is the recommended approach for adding a new field, with a default, to an existing DB? A migration step?
I have added a new field to my model:
@property({
type: 'boolean',
default: true,
})
isFoo: boolean;
Now, when I fetch an old existing document from the collection, I want to see it has the property isFoo: true. How can we achieve that?
Plan A: I fetch pre-existing instances of the model from MongoDB, but this new property is not present on the objects.
I was hoping to see isFoo: true on the objects I had fetched.
Plan B: I fetched a document, and saved it back:
const document = await myModelRepository.findOne({});
await myModelRepository.update(document!);
I was hoping doing this would set the default, storing isFoo: true in the DB.
But I actually saw isFoo: null stored in the DB. 😱 That seems like a violation of contract to me! (Given that Loopback's validation would reject such a document if I sent it to the API.)
Plan C: I tried to manually update the documents in the DB, using the defaults from new Model() but that object has no fields set at all, so I cannot see what the default value is supposed to be.
Plan D: This is what I have needed to do in practice (and also what derdeka mentions below). I run a migration script that checks for any documents with that field unset, and updates the value to true.
(This is basically Plan C except in Plan C I found I was unable to get the default value out of the model, so I have to hard-code the true in my migration script.)
This has a few disadvantages, not least of which needing to repeat the value true in a second place in the codebase. (Violates SSOT)
Another difficulty is finding these documents. For small collections, I simply do a find({}) and then loop them. But for larger collections, I found the query { field: { exists: false } } doesn't find the documents. For string fields, I found I had to do { field: { nlike: '.' } }. (I am currently working with @loopback/repository@2.8.0)
This was easy with Mongoose: Whenever documents were fetched, or written, any missing fields were populated with the default values.[1] That seems to me like desirable behaviour, which would be good in Loopback.
But as far as I can tell, Loopback 4 is only populating default values when I create a new document.
Is that the expected behaviour?
I built a fresh example project to ensure it wasn't just a problem with my project.
What am I asking for?
-
Discuss whether populating default values should be a feature when reading and writing entities, instead of only when creating. (This is what mongoose does, and it I have found it quite useful.)
-
Alternatively, please recommend how to achieve the population of new fields when needed (e.g. as a migration step).
-
Document the default property on the models page, when it is applied, and how to migrate old documents. (The current documentation explains what it is, but not what it does.)
[1] Mongoose will skip population of defaults with sparse: true or validate: false options, useful when dealing with large numbers of records.
TLDR: Loopback uses the
defaultfor a field when a document is created with that field missing.But wouldn't it be useful to also fill out that field when reading and writing documents?
(E.g. if a new field is added, but documents in the DB do not have a value for that field.)
If not, then what is the recommended approach for adding a new field, with a default, to an existing DB? A migration step?
I have added a new field to my model:
Now, when I fetch an old existing document from the collection, I want to see it has the property
isFoo: true. How can we achieve that?Plan A: I fetch pre-existing instances of the model from MongoDB, but this new property is not present on the objects.
I was hoping to see
isFoo: trueon the objects I had fetched.Plan B: I fetched a document, and saved it back:
I was hoping doing this would set the default, storing
isFoo: truein the DB.But I actually saw
isFoo: nullstored in the DB. 😱 That seems like a violation of contract to me! (Given that Loopback's validation would reject such a document if I sent it to the API.)Plan C: I tried to manually update the documents in the DB, using the defaults from
new Model()but that object has no fields set at all, so I cannot see what the default value is supposed to be.Plan D: This is what I have needed to do in practice (and also what derdeka mentions below). I run a migration script that checks for any documents with that field unset, and updates the value to
true.(This is basically Plan C except in Plan C I found I was unable to get the default value out of the model, so I have to hard-code the
truein my migration script.)This has a few disadvantages, not least of which needing to repeat the value
truein a second place in the codebase. (Violates SSOT)Another difficulty is finding these documents. For small collections, I simply do a
find({})and then loop them. But for larger collections, I found the query{ field: { exists: false } }doesn't find the documents. For string fields, I found I had to do{ field: { nlike: '.' } }. (I am currently working with@loopback/repository@2.8.0)This was easy with Mongoose: Whenever documents were fetched, or written, any missing fields were populated with the default values.[1] That seems to me like desirable behaviour, which would be good in Loopback.
But as far as I can tell, Loopback 4 is only populating default values when I create a new document.
Is that the expected behaviour?
I built a fresh example project to ensure it wasn't just a problem with my project.
What am I asking for?
Discuss whether populating default values should be a feature when reading and writing entities, instead of only when creating. (This is what mongoose does, and it I have found it quite useful.)
Alternatively, please recommend how to achieve the population of new fields when needed (e.g. as a migration step).
Document the
defaultproperty on the models page, when it is applied, and how to migrate old documents. (The current documentation explains what it is, but not what it does.)[1] Mongoose will skip population of defaults with
sparse: trueorvalidate: falseoptions, useful when dealing with large numbers of records.