We've got a lot of choices for how we implement TrivialModels. This is my attempt to make it through some of them.
import { Model, types, drivers } from 'trivialmodels';
class UserModel extends Model
{
// This is required, or it throws an error.
get $schema()
{
return {
name: types.String(),
email: types.String({ validator: this.checkEmail }),
admin: types.Boolean({ default: false })
};
} // end $schema
// Set the database for this model instance
// This is required, or it throws an error.
get $db(){ return drivers.trivialdb('users', { pk: 'email' }); }
// Custom property
get displayName(){ return this.name || this.email; }
// Custom Validation Function
checkEmail(email)
{
var re = /\S+@\S+\.\S+/;
return re.test(email);
} // end checkEmail
testFunc()
{
console.log('custom function');
} // end testFunc
} // end UserModel
After a lot of experimentation, it turns out you can access things defined on the prototype (aka, in the class
declaration) from inside static functions. In this example, accessing the db from inside a static function would be:
this.prototype.$db
. That makes the whole thing work.
The nice thing about this is that it's intuitive to extend the models, or add logic to them. They're more than just a pure data object, now they contain logic. Your model is all inclusive, from the very get-go.
Also, your database is super decoupled here; it's a model, and as long as what you return from the $db
property conforms to
the driver API, it doesn't care where it came from.
The class structure is awkward for this, since in ES6 you can't declare member properties. (The best you get is the ability to set getter/setters, which could be used but are very, very verbose.) So, while this would work, there's unavoidable boilerplate that makes it awkward.
Additionally, this is pretty non-standard for Javascript, and a hige departure from other model systems.
import { Model, types, drivers } from 'trivialmodels';
var UserModel = Model.define({
name: 'UserModel',
driver: drivers.trivialdb({ database: 'some-namespace:users', pk: 'name' }),
schema: {
name: types.String(),
email: types.String({ validator: this.checkEmail }),
admin: types.Boolean({ default: false }),
// Properties
get displayName(){ return this.name || this.email; }
// Functions
testFunc: function()
{
console.log('custom function');
},
checkEmail: function(email)
{
var re = /\S+@\S+\.\S+/;
return re.test(email);
}
}
});
This is, esentially calling a factory function to get back an instantiable mode class. (See this for how to build dynamically names functions, so we can name the model correctly.) This will basically be a custom model instance that is named correctly.
This is a much simpler, and in some ways more elegant way to specify everything we want to be in the generated model. It's an easily extended format, and attempts to maintain all the benefit from the class
based method. Additionally, it removes boilerplate, making the definition of the model very easy to read.
A second benefit, which can't be overstated is the fact that the contract between definition and resulting model is explicit. You know exactly what will be done by TrivialModels for any given structure, whereas with a class
based approach, you're building a class from scratch, which could introduce subtle behavioral differences, or at the very least is much less easy to intuit about.
It's not using classes, so visually it does not conform to the same patterns as classes. You also have to know what sections of the object are supported; if you, say, want to add a method to the model, you must know to add it under methods
. The format is, by necessity arbitrary and proprietary; there is no standard to learn.