Skip to content

ModelsType

Wyatt Greenway edited this page Dec 13, 2022 · 13 revisions

class ModelsType extends RelationalTypeBase 📜

A "virtual" field type that defines a one to many, or many to many relationship with other model(s).

Many to many relationships interact with "sets", which in Mythix ORM is the term used to define an "array of models", in our N to many relationship.

Relationships (also known as "associations") in Mythix ORM are defined by virtual fields that return a "relationship query" from a method known as a "query generator" that is defined along with the type definition. This "relationship query" itself defines the model relationship, no matter how complicated. Other ORMs require that you define relationships via their "through table", matching foreign key, type, etc... In Mythix ORM, everything is simply defined as a query for the relationship.

This field type can be used to define a virtual field that defines a relationship to other models. When your model is instantiated into an instance, then this type will inject the following methods into your model instance to assist you in interacting with the relationship set. The "injected methods" listed below are "prefixes". The full names of these methods are always postfixed with the actual name of your field that defines the relationship. For example, if you had a roles field that defined this type of relationship, then the methods injected would be model.getRoles, model.destroyRoles, model.pluckRoles, etc...

Injected Method Prefix Full Method Name Signature Description
addTo addTo${fieldName} addTo(models: Array<Model | object>, options?: object): Promise<Array<Model>> Create the models defined by the relationship, and add them to the set. Returns the newly created models.
count count${fieldName} count(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<number> Count the number of models in the set.
destroy destroy${fieldName} destroy(options?: object, ...args: Array<any>): Promise<number> Destroy the models defined by the relationship set. Returns the number of models destroyed. Note that this not only destroys the target models, but also any through-table relationships involved.
get get${fieldName} get(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<Array<Model>> Get the models specified by the relationship set. Return the entire model set.
has has${fieldName} has(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<boolean> Check if the relationship set contains any models.
pluck pluck${fieldName} pluck(userQuery?: QueryEngine, fields: Field | string | Array<Field | string>, options?: object, ...args: Array<any>): Promise<Array<any>> Pluck specific fields from the related models (set).
queryFor queryFor${fieldName} queryFor(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<QueryEngine> Get the raw relationship query defined by this field type (returned by the "query generator").
removeFrom removeFrom${fieldName} removeFrom(models: Array<Model>, options?: object): Promise<number> Remove the specified models from the relationship set without destroying the target models (destroy only the through-table linking models). Returns the number of models remaining in the set after the operation.
set set${fieldName} set(models: Array<Model | object>, options?: object): Promise<Array<Model>> Overwrite the model set with the models specified, destroying models outside the specified set of models. Returns the new models.

The ...args provided for each injected method are pass-through args for the user, that allow the user to pass any arguments they desire to the "relational query generator" method defined by the field (the ...userArgs as seen in the example below). One minor exception to this is any method that has a userQuery argument. The userQuery argument will always also be passed as the first argument to the ...userArgs of the "query generator" method. The userQuery argument is to allow the user to define an extra query to merge into the primary/root query of the relationship. For example, in the example provided below, we could call await user.getRoles(Role.where.name.EQ('admin')) to only get the related roles where each role also has a name attribute with the value 'admin'.

This type also injects duplicates of each of these relational methods with an underscore prefix, i.e. _getRoles. These are known as "root methods". They are provided so the user can overload the default implementations. For example, you could define a getRoles method on your model class, and from that call the "super" method simply by calling this._getRoles. This is required because with method injection super doesn't actually exist, because the method was injected, instead of being defined on the model class's prototype.

Example:

  • class User extends Model {
      static fields = {
        ...,
        // Add a virtual relationship field, which creates a
        // one to many relationship with the Role model.
        //
        // Notice how a "type" can always be used directly as
        // the field definition, instead of defining an object
        // with a "type" property.
        roles: Types.Models(
          // Specify the target/root model.
          'Role',
          // Define our "query generator" method that
          // will return our "relationship query".
          (context, connectionModels, ...userArgs) => {
            // Get the model we need from the connection,
            // which is conveniently passed to us as the
            // `connectionModels` argument here.
            let { Role } = connectionModels;
    
            // Get the "self", which is the model instance
            // calling this method
            // (i.e. with `model.getRoles()`, "self" would be "model")
            let { self } = context;
    
            // Now return a relationship query
            return Role.where
              .userID
                .EQ(self.id)
              .AND
              .MERGE(userArgs[0]); // Apply the `userQuery` (will do nothing if nullish)
           },
         ),
       };
     }
    
     // ... later on
     // get the "roles" for the first user in the database
     let user = await User.first();
     // Call relationship method injected by the `Types.Models` type.
     let allUserRoles = await user.getRoles();

Notes:

  • See the Associations article for a more in-depth discussion of Mythix ORM model relationship/associations.
  • The "query generator" method defined with the type can be an asynchronous method.
  • Relational methods will not be injected into the model instance if a method of the same name already exists on the model instance. For example, if you define a getRoles method on your model class, then the default get relational method (getRoles in our example) won't be injected. However, the "root method" _getRoles will still be injected, allowing you to call that as the super method instead.
  • This field type will not be "exposed" to models. This means that your model will not have an attribute with the name provided by the field defining this type. The only thing that will exist on your model will be the relational methods injected by this type. Said another way, and using the examples provided, there will be no "roles" attribute on your User model.

method ModelsType::fieldNameToOperationName(
    field: Field,
    operation: string,
    rootMethod: boolean,
): string
📜

Internal method used to generate injected method names.

This method likely should never be called by the user. It is used to generate the injected method names.

Arguments:

  • field: Field

    The parent field defining this relationship.

  • operation: string

    The operation being injected into the model instance, which is one of the prefixes listed above, i.e. addTo, get, destroy, etc...

  • rootMethod: boolean

    If true, then return a generated "root method" name, instead of a "user method". The only difference is that the "root method" name is prefixed with an underscore _.

Return value: string

The method name to inject into the model instance.


method ModelsType::initialize(
    connection: Connection,
    self: Model,
)
📜

Inject type methods into the model instance.

The Type.initialize method is always called for each model that is instantiated. For this type, this is used to inject the relational methods into the model instance.

Arguments:

  • connection: Connection

    The connection for the model being instantiated.

  • self: Model

    The model instance the type is being initialized for.



Clone this wiki locally