Model relations (The M Project 1.x) - mwaylabs/The-M-Project GitHub Wiki
Overview
When transforming a real world data model into a entity relation model in the digital world, you're always confronted with relations between entities: a car has an engine, a person has an adress, an invoice has one or more order items and so on. In SQL and the world of relational schemas, we use primary key-foreign key references to link different relations with each other. We want this behaviour mapped to the world of models. Therefore two methods in M.Model
are offered to the developer: hasOne
and hasMany
.
hasOne
Syntax of hasOne
:
M.Model.hasOne('<NameOfReferencedModel>', {
// object that contains properties like in M.Model.attr(): validators, isRequired flag
});
Given two entities, Contact
and Address
. One Contact
has one Address
:
Contacts.Contact = M.Model.create({
__name__: 'Contact',
firstName: M.Model.attr('String',{
isRequired:YES
}),
lastName: M.Model.attr('String', {
isRequired:YES
}),
/* Model Reference */
address: M.Model.hasOne('Address', {
isRequired:NO
})
}, M.DataProviderLocalStorage);
Contacts.Address = M.Model.create({
__name__: 'Address',
street: M.Model.attr('String', {
isRequired: YES
}),
number: M.Model.attr('String', {
isRequired: YES
}),
zip: M.Model.attr('Integer', {
isRequired:NO,
validators: [M.NumberValidator]
}),
city: M.Model.attr('String', {
isRequired: YES
})
}, M.DataProviderLocalStorage);
Assigning a model record to a reference
Assigning a model record as reference to another model record is as easy as passing values to all the other properties. In the controller we write:
var a = Contacts.Address.createRecord({
street: '5th avenue'
number: '47',
zip: '10019',
city: 'New York City'
});
a.save();
var c = Contacts.Contact.createRecord({
firstName: 'Johnny',
lastName: 'Walker',
address:a /* assigning address model record to contact => contact ---hasOne---> address */
});
c.save();
We could have called save
on a
also after c
has been saved or we could have used cascaded save as described below.
If we want to set the address reference property after the creation of the contact, we can use the Model's set
method like we use it on every other property:
c.set('address', a);
The model automatically knows how to proceed with references and assigns the m_id
to the model record.
Hint: Only the reference to the other model record is saved with the model record, not the whole object.
Lazy loading of references
When we load a model record from the database, e.g. a contact, the model record only contains the references to the other model records. In our example, a loaded contact would have the reference to the address loaded, but not the address itself. Imagine a huge model with many references to other model records: do we really want to have all them implicitly loaded, even if we might not use any of them? We say: "By default, No.". But do we have to load them by hand respectively by other find calls? We can do it like this, but we don't have to: now here's where get
and especially complete
enter the stage. While get
lazy loads only the one property where get
is directed to, complete
does it for all record properties that are references.
get
What if we call ``get` on a model record to retrieve the value of a property and this property represents a reference to another model record? There are three possible options:
- If the referenced entity already is loaded and assigned to the model record, this entity is returned.
- If the referenced entity is not loaded yet, but a reference is set, there's another two possible options:
If the force flag was passed to
get
(as seen in the code example below), the entity is loaded from storage. Then it is assigned to complete this one reference. If the force flag is not passed toget
, a check is performed to see if the entity is already loaded and available in the model'sM.Model.RecordManager
. If it is available there, the entity object is returned, if not,NO
is returned, indicating that the reference is set, but not yet loaded. - If no reference is set yet,
null
is returned.
var c = Contacts.Contact.find({key:1}); // we assume the contact above was saved with key '1' in LocalStorage
var addressOfC = c.get('address', {force:YES}); // get now forces a load, if the entity is not yet set
console.log(addressOfC.get('city')); // print out city property of address
=> New York City
complete
Think of a scenario where we want every Contact
to be complete, which means all references are established and we accept the performance price of loading all references of a model record. We can use the models complete
method to fulfill this task:
var c = Contacts.Contact.find({key:1}); // we assume the contact above was saved with key '1' in LocalStorage
c.complete();
var addressOfC = c.get('address'); // address now contains the address model record that is referenced by c
console.log(addressOfC.get('city')); // print out city property of address
=> New York City
When using M.WebSqlProvider
as data provider we pass a callback function with the complete
call, which is executed when the completion was successful.
c.complete({function(){ console.log('Completion successful! Now proceed...'); }});
Cascading save
Let's assume we have a model with many relations to other models. We created a model record of this and assigned other records to each of the references the model record has. Now we want to save this model record, but also all the other assigned records. We can do it manually and there's nothing evil about doing so - except that we have to write the save
call multiple times. But we like to shorten such tasks, so we're able to just pass a flag to the save
call, indicating that it shall be cascaded to all references:
var m = Example.MyHugeModelWithManyReferences.create({. . .}); // a model record with lots of references to other model records
m.save( { cascade:YES } );
Now all referenced model records are saved, too.