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
addRelationadvanced
The 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
getRelationadvanced
The 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'
)