Add a relationship

You can add different type of relationships between models, being able to retrieve associated models from one another.

1:1

  • A User has one Address and an Address belongs to one User (1:1)
var Address = ziti.define('Address', {
    street: ziti.String,
    number: ziti.Int
});

var User = ziti.define('User', {
    name: ziti.String,
    address: Address
});

// OR

var User = ziti.define('User', {
    name: ziti.String,
    address: ziti.One(Address)
});

This means:

  • A foreign key is added to the target model (Address) matching the primary key of the source (User) if not already exists. Here a user_id field will be added to the Address model.
  • An Address instance is also retrieved when retrieving the source.
  • There are several options to customize this relationship (See One)

Example:

User.at({ id: 1 }).then(function (user) {
    console.log(user.raw());
    /*
       { id: 1, name: 'Alex', address: { street: 'jump street', number: 22 } }
    */
    return user.address.update({ number: 23 });
});

1:n

  • A User has many Photo and a Photo belongs to one User (1:n)
var Photo = ziti.define('Photo', {
    path: ziti.String
});

var User = ziti.define('User', {
    name: ziti.String,
    address: Address,
    photos: [ Photo ]
});

// OR

var User = ziti.define('User', {
    name: ziti.String,
    address: ziti.One(Address),
    photos: ziti.Many(Photo)
});

This means:

  • A foreign key is added to the target model (Photo) matching the primary key of the source (User) if not already exists. Here a user_id field will be added to the Photo model.
  • An array of Photo instances is also retrieved when retrieving the User.
  • There are several options to customize this relationship (See Many)

Example:

User.at({ id: 1 }).then(function (user) {
    console.log(user.raw());
    /*
       {
          id: 1,
          name: 'Alex',
          address: { street: 'jump street', number: 22 },
          photos: [ { path: 'hello.jpg' }, { path: 'world.jpg' } ]
       }
    */
    return user.photos[1].update({ path: 'foo.jpg' });
});

n:m

  • A User has many Language and a Language has many User (n:m)
var Language = ziti.define('Language', {
    name: ziti.String
});

var User = ziti.define('User', {
    name: ziti.String,
    address: Address,
    photos: [ Photo ],
    langs: [ Language, 'UserLanguage' ]
});

// OR

var User = ziti.define('User', {
    name: ziti.String,
    address: ziti.One(Address),
    photos: ziti.Many(Photo),
    langs: ziti.Many(Language).through('UserLanguage')
});

This means:

  • An intermediary model (UserLanguage) is created if not exists
  • A foreign key is added to the intermediary model (UserLanguage) matching the primary key of the source (User) and a foreign key matching the primary key of the target (Language) if not already exists. Here user_id and language_id fields will be added to the UserLanguage model.
  • An array of Language instances is also retrieved when retrieving the User.
  • There are several options to customize this relationship (See Many)

Example:

User.at({ id: 1 }).then(function (user) {
    console.log(user.raw());
    /*
       {
          id: 1,
          name: 'Alex',
          address: { street: 'jump street', number: 22 },
          photos: [ { path: 'hello.jpg' }, { path: 'world.jpg' } ],
          langs: [ { name: 'french' }, { name: 'anglais' } ]
       }
    */
    return user.lang[1].update({ name: 'english' });
});

Foreign keys

It is also possible to provide an explicit foreign key to your models.

var Address = ziti.define('Address', {
    street: ziti.String,
    number: ziti.Int,
    owner: ziti.ForeignKey('User')
});

This means:

  • A foreign key is added to the source model (Address) matching the primary key of the target model (User) if not already exists. Here a owner_id field will be added to the Address model.
  • A User instance is also retrieved when retrieving an Address.
  • There are several options to customize this relationship (See ForeignKey)

Example:

Address.at({ street: 'jump street', numero: 22 }).then(function (address) {
    console.log(address.raw());
    /*
    { id: 1, street: 'jump street', numero: 22, owner: { id: 1, name: 'Alex' } }
    */
    return address.user.remove();
});

Options

It can be convenient to retrieve a related model using custom options.

on

You can specify which fields are used to associate models. For example, you may want to associate an Address to a User name rather than its id:

var User = ziti.define('User', {
    name: ziti.String,
    address: ziti.One(Address).on({ name: 'user_name' })
});

This tells that a user_name field will be added to the Address model related to the User name field.

relatedName

Since you can declare an explicit ForeignKey when defining a model, it is necessary to identify it when refering the foreign key in another Model. It links relationships between two models. For example:

var Address = ziti.define('Address', {
    street: ziti.String,
    number: ziti.Int,
    owner: ziti.ForeignKey('User')
});

var User = ziti.define('User', {
    name: ziti.String,
    address: ziti.One(Address).relatedName('owner')
});

Retrieving associations

By default, associations defined in a Model are also included when retrieving data from this Model. It is possible to define your own scope to only retrieve the Model fields you want. For example:

var User = ziti.define('User', {
    name: ziti.String().$('profile'),
    address: ziti.One(Address).relatedName('owner')
});

User.at({ name: 'Alex' }).$('profile').raw()
    .then(function (user) {
        console(user);
        /*
        { name: 'Alex' }
        */
    });

It is also possible to retrieve a specific field that belongs to a reference using reference.field notation. For example:

User.at({ name: 'Alex' }).only('name', 'address.street').raw()
    .then(function (user) {
        console(user);
        /*
        { name: 'Alex', address: { id: 1, street: 'jumpstreet' } }
        */
    });