Skip to content

Model

Fundamentals

Constructors

Prevue models do not have constructors, and it is strongly recommended not to create any. There are multiple internal features that take advantage of the fact, that the models can be freely created without passing any arguments into the constructors.

It is technically possible, and should be harmless, to create a constructor that simply initializes model's properties (without accepting any arguments), but bear in mind that the models are created many times under-the-hood, for example during the DTO conversion. Therefore, using any kind of auto-incrementing counters inside constructors might lead to confusion, as its value will end up being larger than the number of instances of that model.

Generally speaking, if you create a constructor, everything that happens inside that constructor will happen multiple times even when simply creating or updating a single model.

Default Values

Since the models do not use constructors, all properties should be assigned default values during declaration in the class.

The following is an example of properly defined model:

typescript
import { Model } from '@kovalson/prevue';

class User extends Model {
  readonly id: string = '';
  readonly firstName: string | null = null;
  readonly lastName: string | null = null;
  readonly email: string = '';
  readonly loginCount: number = 0;
}

Technically, you don't need to assign any default values, but this might lead to many misunderstandings. Unassigned properties become undefined, so you lose control over the real, runtime type. For example, you might be tempted to use the non-null asserting operator which, at first, seems to spare some boilerplate:

typescript
import { Model } from '@kovalson/prevue';

class User extends Model {
  readonly firstName!: string;
}

Such model description assumes that any user's object that comes from the server has the firstName property and that property is a string. This might be true for what the server returns, but if you were to create a model, that model's firstName property would be undefined:

typescript
const user = new User();
console.log(user.firstName);
// user.firstName is undefined

In this case, you would have to get around it by creating a helper method or function that creates a valid empty user. For example:

typescript
import { Model, createInstance } from '@kovalson/prevue';

class User extends Model {
  public static create(): User {
    return createInstance(User, {
      firstName: '',
    });
  }
  
  readonly firstName!: string;
}

But this starts to be much more troublesome than simply initializing the property with a default value.

Other Models

If your model relates to other models, you also need to create default values for them. In such case, simply create an empty instance of that model.

The following is an example of a properly defined model with relation to other model:

typescript
import { Model } from '@kovalson/prevue';
import { User } from '~/models/User';

class Post extends Model {
  readonly id: string = '';
  readonly title: string = '';
  readonly content: string = '';
  readonly author: User = new User();
  
  protected $relations = {
    author: User,
  };
}

Mutability

Prevue aims to keep models immutable, but does not yet freeze their instances. Nevertheless, it is highly recommended to mark all models' properties as readonly as suggested in all examples to avoid compile-time mistakes at the very least.

Released under the MIT License.