# Conventions
In order to leverage the power of Dobby models, it might be useful to understand some basics.
# Model Key
Model keys are similar to model names in that they're used to identify models. By default, you may not define a model with the same name more than once. Using a key identifyie is a way to define multiple models that share the same name but might have different schemas.
In some cases, especitially when working with multiple client endpoints, you might need to define multiple models with different schemas.
Model 1.
export class Address extends Model{
static get modelKey(){
return 'UserAddress'
}
static fields(f) {
f.id()
f.string('street')
f.string('street_extra')
...
}
}
Model 2
export class Address extends Model{
static get modelKey(){
return 'LocationAddress'
}
static fields(f) {
f.id()
f.string('line1')
f.string('line2')
...
}
}
Defining the above models at different locations in one application without unique modelKey
will cause several schema build errors.
With the default adapter, the below will execute a addresses{ id }
query for either models.
const addresses = await Address.select("id").findMany();
# Primary Keys
Dobby assumes your models have a primary key named id
.
When defining your model's primary key, it's recommended to use the ID
attribute to automatically make it read-only.
import { Model } from "@moirei/dobby";
class User extends Model {
static entity = "User";
static primaryKey = "handle";
static fields(f) {
f.id();
// Or
f.id("handle", "ID");
}
}
If you need to retrieve the primary key name or value on a model instance, you may use the $getKeyName
and $getKey
methods.
const user = new User({ handle: "james-franco" });
user.$getKeyName(); // returns "handle"
user.$getkey(); // returns "james-franco"
# Default Selects
By default, all model attributes are selected in queries. For models with large number of attributes, you might want to limit the number of query fields selected by default. E.g. when using the findMany
method. Use the queryAttributes
property to specify the default selection.
class User extends Model {
/**
* Entity name
*/
static entity = "User";
/**
* Attributes to always include in queries by default.
*/
static queryAttributes: string[] = ["id", "name"];
}
# Eager Loading
By default, all relationships are excluded in queries. Use the queryRelationships
property to automatically include relationships in queries.
class User extends Model {
/**
* Entity name
*/
static entity = "User";
/**
* Attribute relationships that should be eager-loaded.
*/
static queryRelationships: string[] = ["posts"];
}
# Dynamic Query Operations
Enabling dynamic query operations allow you to build and execute queries in style. Assuming you need to perform a query named subscribedUsers
, you may not necessarily want to create a new Adapter or hook for this.
class User extends Model {
/**
* Entity name
*/
static entity = "User";
/**
* Allow performing dynamic actions on queries.
*/
static dynamicQueryOperations: boolean = true;
}
Now, the following query can be called without throwing errors
await User.select(["id", "name"]).subscribedUsers();
This will build and execute the following query
query {
subscribedUsers {
id
name
}
}
Likewise a mutation for subscibeUser
can be
await User.where("id", 1).mutation().subscibeUser();
This will build and execute the following query
mutation ($id: Int) {
subscibeUser(id: $id)
}
# Default Attribute Values
One of the ways to define default values in using the model's original
attributes bucket. When you create or retrieve a model, the raw values of the retrieved fields are stored here.
class User extends Model{
static entity = 'User';
protected original = {
email_verified: false
}
}
Avoid setting default values to primary keys.
Defaults can also be set at the field definition level.
# Maximum Query Depth
With eager loading enabled, related models are automatically included to queries. By default this is limited to 3 degrees.
class User extends Model {
static entity = "User";
/**
* The maximum query depth.
* Used to limit nested included relationships.
*/
static maxQueryDepth = 3;
}
This is ignored when relationships are included manually.
# Make Arguments Required by Default
By default, anonymous variables are automatically parsed. That is, the following query will consider id
to be an optional variable.
await User.where("id", 1).mutate("subscibeUser");
mutation ($id: Int) {
subscibeUser(id: $id)
}
To make all anonyous variables required by default, set the argumentRequiredByDefault
property to true
.
class User extends Model{
static entity = 'User';
/**
* Make auto-resolved argument types required by default.
*/
public static readonly argumentRequiredByDefault = true;
}
Now the above mutation with produce a slightly deferent query
await User.where("id", 1).mutate("subscibeUser");
mutation ($id: Int!) {
subscibeUser(id: $id)
}
Note that anonymous variables is only allowed for primitive types.
# Fetch Policy
Models can configure their fetch policy for Apollo Client. By default this is set to no-cache
. Fetch policies can also be changes at the query level.
class User extends Model{
static entity = 'User';
/**
* The default fetch policy for the model.
*/
public static readonly fetchPolicy = "network-only";
}
User.query().policy("cache-and-network").findMany();
# Attribute Changes
Dobby provides $isDirty
and $isClean
methods to help check the state of the a model instance compared to its initiate state when the model was retrieved or created.
const user = await User.findUnqiue({ id: 1 });
user.name = "John";
user.$isDirty(); // true
user.$isDirty("name"); // true
user.$isDirty("email"); // false
user.$isClean(); // false
await user.$save();
user.$isDirty(); // false
To include dirty state of related models, use the $isDeepDirty
method.
if (user.$isDeepDirty()) {
//
}
If you would like to keep the changes without calling $save
, you can use the $keepChanges
method.
user.name = "John";
user.$keepChanges();
user.$isDirty(); // false
# Copying and Hydration
It is possible to define a model and later hydrate its content with a query result.
const user1 = new User({
id: 1,
name: "Joe Blow",
});
const user2 = new User();
This will perform a findUnique
operation under the hood and then copy its results.
await user2.$hydrateWith({ id: 2 });
The $hydrateWith
method supports arguments similar to the findUnique
method.
Now that user2
is hydrate, its content can also be copied to user1
.
user1.$copy(user2);
user1.id; // returns "2"
user1.name; // returns "John Doe"
# Comparing Models
Dobby provides methods $is
and $isNot
to quickly compare models. It compares the Model keys and primary keys.
if (user.$is(anotherUser)) {
//
}
if (user.$isNot(anotherUser)) {
//
}
# Retrieving Instance Constructor
For convenience, model instances can retrieve their constructor with the provided $self
method.
const user = new User();
user.$self(); // returns "class User extends Model{..}"
← Defining Models CRUD →