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:

  1. If the referenced entity already is loaded and assigned to the model record, this entity is returned.
  2. 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 to get, a check is performed to see if the entity is already loaded and available in the model's M.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.
  3. 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.