Relationships
Upfront helps with parsing, handling and querying related data just like you would in your backend framework. To allow such easy access first you have to define your relations on the model.
Define a relationship
The following relationships are available:
These relation methods are somewhat reflecting of how the backend data relations have been set up. It is reasonable to think that relations of any complexity can be handled by the above as upfront doesn't need to be aware of how a distant relation is related. The methods return their target model with the endpoint set to the expected values.
To define a relationship you can call the appropriate relations on the model.
import { Model } from '@upfrontjs/framework';
import Shift from '@Models/Shift';
export default class User extends Model {
    $shifts() {
        return this.belongsTo(Shift)
    }
}
Then you may query as:
import User from '@Models/User';
const user = User.make({ id: 1 });
const shiftsWithColleagues = await user.$shifts().with('colleagues').get(); // ModelCollection
await user.load('contract');
user.contract; // Contract
Notice that relationship methods has to start with the defined relationMethodPrefix. This will ensure that they can be distinguished from their accessor counterpart.
Relation Types
belongsTo
The belongsTo method describes a 'belongs to' relationship in the database. It takes two arguments, the first being the related model's constructor, and the second optionally the foreign key's name.
// User.ts
import { Model } from '@upfrontjs/framework';
import Team from '@models/Team';
export default class User extends Model {
    public $team(): Team {
        return this.belongsTo(Team);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const team = await user.$team().get();
belongsToMany
The belongsToMany method describes a 'belongs to many' relationship in the database. It takes two arguments, the first being the related model's constructor, and the second optionally the relation name that is used on the back end.
WARNING
This functionality depends on the back end being capable parsing nested where queries.
// User.ts
import { Model } from '@upfrontjs/framework';
import Role from '@models/Role';
export default class User extends Model {
    public $roles(): Role {
        return this.belongsToMany(Role);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).with().get();
const userRoles = await user.$roles().get();
hasMany
The hasMany method describes a 'has many' relationship in the database. It takes two arguments, the first being the related model's constructor, and the second optionally the foreign key name on the related model.
// User.ts
import { Model } from '@upfrontjs/framework';
import Comment from '@models/Comment';
export default class User extends Model {
    public $comments(): Comment {
        return this.hasMany(Comment);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const userComments = await user.$comments().get();
TIP
hasMany and hasOne methods also allows us to create related resources while automatically setting the related attribute value.
// User.ts
export default class User extends Model {
    public $grades(): Grade {
        return this.hasMany(Grade);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const grade = user.$grade();
grade.userId; // 1
grade.save({ value: 'A+' }); // post body: { value: 'A+', user_id: 1 }
hasOne
The hasOne method describes a 'has one' relationship in the database. It takes two arguments, the first being the related model's constructor, and the second optionally the foreign key name on the related model.
// User.ts
import { Model } from '@upfrontjs/framework';
import Car from '@models/Car';
export default class User extends Model {
    public $car(): Car {
        return this.hasMany(Car);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const userCar = await user.$car().get();
morphMany
The morphMany method describes a relation to a polymorphic entity. The method takes two arguments, the first being the related model's constructor, and the second optionally the morph name used for associating to the current model.
// User.ts
import { Model } from '@upfrontjs/framework';
import File from '@models/File';
export default class User extends Model {
    public $documents(): File {
        return this.morphMany(File);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const userDocuments = await user.$documents().get();
morphOne
The morphOne method describes a relation to a polymorphic entity. The method takes two arguments, the first being the related model's constructor, and the second optionally the morph name used for associating to the current model.
// User.ts
import { Model } from '@upfrontjs/framework';
import File from '@models/File';
export default class User extends Model {
    public $passport(): File {
        return this.morphOne(File);
    }
}
// myScript.ts
import User from '@models/User'
const user = await User.limit(1).get();
const userPassport = await user.$passport().get();
morphTo
The morphTo method describes a polymorphic relation and expects one argument. A callback where the correct related model constructor is returned depending on the provided logic. This callback receives the polymorphic parent and the attributes of the relation to help choosing the correct model.
TIP
morphTo is a special case as this method returns the morph parent itself as opposed to the relation's model. This is because the morphed model is not expected to implement the standard REST endpoints.
// Contract.ts
import { Model } from '@upfrontjs/framework';
import Car from '@models/Car';
import Team from '@models/Team';
export default class Contract extends Model {
    public contractableId?: number;
    public contractableType?: 'team' | 'car';
    public contractable?: Team | Car;
    
    public $contractable(): this {
        return this.morphTo((self, attributesOfRelation) => {
            return self.contractableType === 'team' ? Team : Car; 
        });
    }
}
// myScript.ts
import Contract from '@models/Contract'
const contract = await Contract.find(1);
// same contract as above fetched from the API, with the relation set
const contractedEntity = await contract.$contractable().get<Contract>().then(contract => contract.contractable);
Manage Relations
addRelation
advancedThe addRelations method adds the relation onto the current model. It accepts two arguments, the first being the name with or without the relationMethodPrefix, and the second the relation data in the format of an object, model class, array or collection.
import User from '@Models/User';
import Contract from '@Models/Contract';
const user = User.make({ id: 1 });
user.addRelation('shifts', { id: 1 });
user.shifts; // ModelCollection[Shift]
user.addRelation('$contract', Contract.make({ id: 1, user_id: 1 }));
user.contract; // Contract
getRelation
advancedThe getRelation method returns the value of the given relation in a checked manner. This is mostly used internally and includes exceptions.
getRelations
The getRelations method returns a deep clone of all the relations currently on the model.
removeRelation
The removeRelation method removes the given relation from the model.
import User from '@Models/User';
const user = User.make({ contract: { id: 1 } });
user.relationLoaded('contract'); // true
user.removeRelation('contract').relationLoaded('$contract'); // false
relationLoaded
The relationLoaded method determines whether the given relation has been loaded or not.
import User from '@Models/User';
const user = User.make({ contract: { id: 1 } });
user.relationLoaded('contract'); // true
user.relationLoaded('$shifts'); // false
loadedRelationKeys
The loadedRelationKeys return an array of the relations keys that are currently available on the model.
Auxiliary methods
relationMethodPrefix
The relationMethodPrefix is a getter on the model that is used to identify relationship calls. All relationship definitions have to start with the set value. The default value is '$'.
import { Model } from '@upfrontjs/framework';
import Shift from '@Models/Shift';
export default class User extends Model {
    get relationMethodPrefix() {
        return '$';
    }
}
for
The for method is used for setting custom endpoints for the next request using the given set of models. It is generally used in custom use cases where the api is not designed to return the desired data in the expected endpoint.
import User from '@Models/User';
user.for(Team.make({ id: 1 })); // 'teams/1/users'
user.for([Team.make({ id: 1 }), Contract.make({ id: 1 })]); // 'teams/1/contracts/1/users'
user.for([new Team, Contract.make({ id: 1 })]); // 'teams/contracts/1/users'
user.for([Team, Contract]); // teams/contracts/users
Overwrites
These methods are used internally to do some work for your. If they are not guessing the values correctly, you may overwrite them in your model class.
getMorphs
The getMorphs method is utilised by the morphOne and morphMany methods. It is used to figure out the foreign key and foreign entity name columns on the morph relations.
- Tag->- 'taggable_type',- 'taggable_id'
- Like->- 'likeable_type',- 'likeable_id'
- etc...
guessForeignKeyName
The guessForeignKeyName is used to figure out the column name used on other tables for the current model.
- User->- 'user_id'(The model's getKeyName returns- 'id')
- Contract->- 'contract_uuid'(The model's getKeyName returns- 'uuid')