When defining a model code-first, one can specify a dynamic default value for properties:
registrationDate: {type: Date, default: function () { return new Date() }},
This is not possible to achieve when using app.boot() and models.json. This is the currently recommended solution:
app.models.Todo.definition.rawProperties.created.default =
app.models.Todo.definition.properties.created.default = function() {
return new Date();
};
@raymondfeng suggested there should be a helper method making this easier.
Related: https://groups.google.com/forum/#!topic/loopbackjs/tIlPMxVZXwE
models.json should be reserved for primitive definition. For advanced usage you should create a models/my-model.js file and extend the MyModel constructor with any advanced behavior (eg. remote methods, dynamic proprerties, getters, setters, etc).
However I don't think your case is "advanced". I suggest we implement some support for created / updated properties in models.json / LDL.
Maybe we should introduce a set of built-in default functions and reference them in models.json. For example:
$now for Date
$uuid for String
Maybe we should introduce a set of built-in default functions and reference them in models.json. For example: $now for Date, $uuid for String
That looks promising. We have to use a different option key though, to prevent possible conflicts.
{ registrationDate: {type: Date, dynamicDefault: '$now' } }
// or
{ registrationDate: {type: Date, computedDefault: '$now' } }
// or
{ registrationDate: {type: Date, defaultFn: '$now' } }
Once this feature is implemented, the following places in loopback core can use the feature too:
AccessToken.properties.createdApplication.properties.created and Application.properties.modifiedNote: it is not enough to set settings.properties.{name}.default, as that value is dropped by ModelDefinition.prototype.defineProperty, which is called e.g. when a relation is set up. One has to override both properties and rawProperties to get correct behaviour.
We can introduce a method to ModelDefinition.prototype, for example:
ModelDefinition.prototype.updateProperty(name, changes);
A super simple solution to cover the most frequent usage with the current time:
if (prop.type === Date && prop.default === '$now')
prop.default = Date;
Since $now is not a valid Date, this solution will not break any existing code and cannot create any confusion.
+1
@violet-day would you mind submitting a pull request implementing this feature?
Here is my proposal for the full solution that will allow us to handle default, validation, middleware, hook and other places that require a custom function as part of the JSON config.
$myFunc or myFunction().app.utility(name, fn) to register utility functions to the appWe introduce a syntax to reference JS functions by name, for example,
$myFuncormyFunction().
As for syntax for referring JS functions by name, I'd rather use a different property name instead of encoding the value. E.g. { registrationDate: {type: Date, defaultFn: 'myFunction' } }
The question is - are we going to execute (eval) the code specified by defaultFn, or does it have to be a name of a nullary function (a fn with no arguments)?
The rest of the proposal looks good to me. Note that the full solution is not a "Beginner Friendly" feature.
However, I still think it is worth it to add a special shortcut for specifying current date as the default value, since it is so frequently used. That part is a "Beginner Friendly" task.
For a property that can take either a function or a value, there are two options:
1 is a bit magic while 2 is a bit verbose. I slightly prefer 1 but I'm fine with 2.
We will NOT eval. The function has to be a pure name. There is no need to embed a function body in the JSON as 2-5 will allow you to define referenceable function.
We can ship a list of built-in functions, such as Date. The function resolver can resolve it to Date
My concern is that special prefix/suffix can conflict with a perfectly valid value. That's why I prefer to use a different property name. The difference between default: '$myFunction' vs defaultFn: 'myFunction is only one character ;-) Though I have to admit that my proposal requires us to change all modules working with property types, most notably loopback-workspace and strong-studio.
We will NOT eval. The function has to be a pure name. There is no need to embed a function body in the JSON as 2-5 will allow you to define referenceable function.
:+1:
The following code is not a workaround since it is only executed when creating the object, so that the updatedAt data never updates. It is a workaround for the createdAt timestamp though.
app.models.Todo.definition.rawProperties.updated.default =
app.models.Todo.definition.properties.updated.default = function() {
return new Date();
};
_Editing_ to inverse those fields and to clarify.
One workaround is to also have:
Model.beforeUpdate = function(next, modelInstance) {
if (modelInstance.updatedAt) {
modelInstance.updatedAt = new Date();
}
next();
};
The following code is not a workaround since it is executed everytime causing the created date to change.
Could you please provide more details, ideally a small example that we can run to reproduce the problem?
Hi, Did we reach on any conclusion (and also implemented) a way to set default value for a model?
I want to set created and lastupdated fields.
I am currently using hooks. Let me if that's the right way to set default value.
Ta.
The implementation of $now as a Date default value has landed in juggler's master: https://github.com/strongloop/loopback-datasource-juggler/pull/445
@inawlaljar I want to set created and lastupdated fields.
You can use $now for the lastupdated field once the new juggler version is released. created field is tricky to get right, because createOrUpdate does not know in advance whether the created column should or should not be updated.
You can use
$nowfor thelastupdatedfield once the new juggler version is released.createdfield is tricky to get right, becausecreateOrUpdatedoes not know in advance whether the created column should or should not be updated.
My last comment was rather incorrect, let me correct.
There is no out-of-the-box way how to implement lastupdated model property. The right approach is to use a before save hook, indeed.
The created property can be implemented with default: '$now' and it will work as long as the calls of updateOrCreate are always passing the full object data retrieved from the database in the "update" case.
// this works and preserves the value of the "created" property
Product.findById(existingId, function(err, prod) {
prod.name = 'renamed';
Product.updateOrCreate(prod);
});
// this updates the value of the "created" property to the current time
var prod = new Product({ id: existingId, name: 'renamed' });
prod.updateOrCreate(prod);
FYI, https://github.com/strongloop/loopback-datasource-juggler/pull/458 implemented defaultFn option that accepts one of guid, uuid or now.
I haven't quite figured it out but I have deployed a sample app on OpenShift and it fails to start because of this default feature. However, it's fine running locally.
*_/var/lib/openshift/570be5c42d5271676e00002e/app-root/runtime/repo/node_modules/loopback/common/models/application.js:70
Application.definition.rawProperties.created.default =
^
TypeError: Cannot assign to read only property 'default' of date *_
@bajtos , concerning the "now" default for Date type, is there a way to set the seconds (and millisec) to 0 ?
It wasn't an issue until I had to implement a client-side generic change detector. If the user change a date or datetime and then set back the original value, a change is still detected because the secs and millisecs are gone.
@Synryu have you tried default: 0? I don't have enough information to meaningfully help you. Please open a new issue and provide us with a small app showing how to reproduce the problem you are experiencing, per our bug reporting instructions.
I am going to lock this conversation down. Please open new GitHub issue(s) if there is anything remaining to discuss.
Most helpful comment
FYI, https://github.com/strongloop/loopback-datasource-juggler/pull/458 implemented
defaultFnoption that accepts one ofguid,uuidornow.