Associations



One-to-One


hasOne(B)

- Utilize (or create if not exist) foreign key in model B’s table
- Create ‘getter’ instance method in model A (named as “getB”)


belongsTo(A)

- Utilize (or creating if not exist) foreign key in model B’s table
- Create ‘getter’ instance method in model B (named as “getA”)

1. For existing relationships (when relevant table constraint already exists in the database)


await Country.hasOne(Capital);
const canada = await Country.where('name = Canada').queryInstance();
const canadaCapital = await canada.getCapital();

console.log(canada);
[
  {
    _id: '00xyz',
    name: 'Ottawa',
    population: '994,837',
    province: 'Ontario',
    country_id: 'canada'
  }
]

await Capital.belongsTo(Country);
const ottawa = await Capital.where('name = Ottawa').queryInstance();
const ottawaCountry = await ottawa.getCountry();

2. Forming new relationships (when you want to make changes to your schema)


import { Model } from 'https://deno.land/x/denogres/mod.ts'

// forming a new relationship between this User and Profile model

interface User {
  id:string;
  firstName:string;
  lastName?:string;
}
class User extends Model {
  static table = 'users';
  static columns = {
    id: { type:'uuid', primaryKey: true },
    firstName: { type:'string', notNull: true },
    lastName: { type:'string', notNull: false },
  }
}

interface Profile {
  id:number;
  email:string;
  address?:string;
}
class Profile extends Model {
  static table = 'profiles';
  static columns = {
    id: { type:'number', primaryKey: true, autoIncrement:true },
    email: { type:'string', notNull: true },
    address: { type:'string', notNull: false },
  }
}

// belongsTo method returns out new association instance

const userProfileAssociation = await Profile.belongsTo(User);

At this point, database tables are not synced yet. Invoking syncAssociation() method on newly created association instance will execute table altering query in database.
Foreign key will be created in 'profiles' table with the default name of 'user_id'.


userProfileAssociation.syncAssociation();

This will execute following command to your database:


ALTER TABLE profiles ADD user_id UUID;
ALTER TABLE profiles ADD CONSTRAINT fk_user_id
FOREIGN KEY (user_id) REFERENCES users
ON DELETE SET NULL ON UPDATE CASCADE;

Creating new association record (beta)


const foo = await User.where('firstname = foo').queryInstance();

const p1 = new Profile();
p1.email = 'foo@foo.com';
p1.address = 'foo Main St';

await foo.addProfile(p1);

This will create new record in profiles table and set the foreign key 'user_id' as the user instances id.

Alternate flow: for existing profile instance when you're aware of the profile's id


await foo.addProfile({ id: 3 })

This will update profile table's 'user_id' foreign key field with the user instance's id.


One-to-Many


One-To-Many association can be formed with 'belongsTo' or 'hasOne' method. For existing association in the database (e.g. existing foreign key constraints), invoking these method will create ‘getter’ method to the model.


belongsTo example


await Person.belongsTo(Species);
const luke = await Person.where('name = Luke Skywalker').queryInstance();
const lukeSpecies = await luke.getSpecies();

// getter method name will be by default : get + target model's name

console.log(lukeSpecies);[
  {
    _id: 1,
    name: "Human",
    classification: "mammal",
    average_height: "180",
    average_lifespan: "120",
    hair_colors: "blonde, brown, black, red",
    skin_colors: "caucasian, black, asian, hispanic",
    eye_colors: "brown, blue, green, hazel, grey, amber",
    language: "Galactic Basic",
    homeworld_id: 9n
  }
]

hasMany example

Update existing rows in the database for the current model.
Input: (...rows: string[ ])


await Species.hasMany(Person);
const droid = await Species.where('name = Droid').queryInstance();
const droidCharacters = await droid.getPersons();

// getter method name will be by default : get + target model's name + 's' (for plural)

console.log(droidCharacters);
[
  {
    _id: 2,
    name: "C-3PO",
    mass: "75",
    hair_color: "n/a",
    skin_color: "gold",
    eye_color: "yellow",
    birth_year: "112BBY",
    gender: "n/a",
    species_id: 2n,
    homeworld_id: 1n,
    height: 167
  },
  {
    _id: 3,
    name: "R2-D2",
    mass: "32",
    hair_color: "n/a",
    skin_color: "white, blue",
    eye_color: "red",
    birth_year: "33BBY",
    gender: "n/a",
    species_id: 2n,
    homeworld_id: 8n,
    height: 96
  },
  {.....(more records...)....}
]

Forming a new one-to-many association:

- Execute belongsTo method first and execute syncAssociation to make call to the database that will alter existing table schema in your database.


import { Model } from 'https://deno.land/x/denogres/mod.ts'

interface User { ... }
class User extends Model {
  static table = 'users';
  static columns = {
    id: { type:'uuid', primaryKey: true },
    firstName: { type:'string', notNull: true },
    lastName: { type:'string', notNull: false },
  }
}
interface Team { ... }
class Team extends Model {
  static table = 'teams';
  static columns = {
    id: { type:'number', primaryKey: true, autoIncrement:true },
    teamName: { type:'string', notNull: true },
    teamRole: { type:'string', notNull: false },
  };
}

Create belongsTo association first. This will attach 'getTeam()' instance method to the User model.

const userTeamAssociation = await User.belongsTo(Team);

Executing syncAssociation() will make an asynchronous call to the database.


userTeamAssociation.syncAssociation();

This will execute following SQL query on your database:


ALTER TABLE users ADD team_id INT
ALTER TABLE users ADD CONSTRAINT fk_team_id FOREIGN KEY (team_id)
REFERENCES teams ON DELETE SET NULL ON UPDATE CASCADE

Then invoke hasMany() method. This will attach 'getUsers()' instance method to the Team model.


await Team.hasMany(User);

Many-to-Many


Unlike other association methods, manyToMany is not a functionality inside the model class, so you need to import to use it. Many-To-Many relationship between two models is through a cross-table (aka. pivot table, through table). For existing Many-To-Many association in the database, you need to specify the model representing the cross-table.


import { manyToMany } from 'https://deno.land/x/denogres/mod.ts'

await manyToMany(Person, Film, { through: PeopleInFilm });

const luke = await Person.where('name = Luke Skywalker').queryInstance();
const lukeFilms = await luke.getFilms();

const newHope = await Film.where('title = A New Hope').queryInstance();
const newHopeCharacters = await newHope.getPersons();

Getter method name will be by default:
Get + target model's name + 's' (for plural)


console.log(lukeFilms)
[
  {
    _id: 1,
    title: "A New Hope",
    episode_id: 4,
    opening_crawl: "It is a period of civil war.\nRebel spaceships, striking\nfrom a hidden base, have won\ntheir first vic...",
    director: "George Lucas",
    producer: "Gary Kurtz, Rick McCallum",
    release_date: 1977-05-25T07:00:00.000Z
  },
  {
    _id: 2,
    title: "The Empire Strikes Back",
    episode_id: 5,
    opening_crawl: "It is a dark time for the\nRebellion. Although the Death\nStar has been destroyed,\nImperial troops hav...",
    director: "Irvin Kershner",
    producer: "Gary Kurtz, Rick McCallum",
    release_date: 1980-05-17T07:00:00.000Z
  },
  { .... (more records...) ... }
]