v0.21

models

introduction

dframework includes an active record implementation for interacting with your database. each database table has a corresponding model that is used to interact with that table. models allow you to query for data in your tables, as well as insert new records into the table, using an elegant and fluent interface.

defining models

to create a model, simply extend the base Model class provided by the framework.

1import { Model } from 'dframework';
2
3export default class User extends Model {
4 //
5}

table names

by default, the framework will use the lowercased, plural name of the class as the table name. for example, the User model will assume a users table exists. if your table does not follow this convention, you may specify a custom table name by overriding the static table property.

1export default class User extends Model {
2 static table = 'system_users';
3}

primary keys

the framework will automatically determine your table's primary key by inspecting the database schema and caching the result. if you want to override this behavior, you can define a static primaryKey property.

1export default class User extends Model {
2 static primaryKey = 'uuid';
3}

composite primary keys are automatically supported if they are defined in the schema.

retrieving models

models proxy all methods from the fluent query builder, allowing you to chain constraints before fetching the results. the all method will retrieve all of the records from the model's table.

1const users = await User.all();

you may use the find method to retrieve a specific record by its primary key.

1const user = await User.find(1);

magic finders

the framework provides dynamic magic methods for retrieving records by a specific column. simply append the column name in camel case to the findBy prefix.

1const user = await User.findByEmail('test@example.com');

pagination

to paginate records, use the paginate method. it automatically reads the page query string parameter from the current request context and constructs the limit and offset constraints.

1const results = await User.where('status', 'active').paginate(15);

the returned object contains the data array alongside pagination metadata like total, current_page, and last_page.

inserting and updating

to insert a new record, you can instantiate a new model instance, set attributes on it, and call the save method.

1const user = new User();
2user.name = 'tarou';
3user.email = 'tarou@example.com';
4await user.save();

mass assignment

alternatively, you can use the static create method to insert a new record and retrieve the instantiated model in a single line. there is no mass assignment protection configuration required; the framework inherently trusts server side model interactions.

1const user = await User.create({
2 name: 'tarou',
3 email: 'tarou@example.com'
4});

to update a model, you can either mutate its properties and call save, or use the update method directly.

1const user = await User.find(1);
2await user.update({ status: 'active' });

first or create

the firstOrCreate method will attempt to locate a record using the given column/value pairs. if the model can not be found, a record will be inserted with the attributes from the first argument, along with any optional attributes from the second argument.

1const user = await User.firstOrCreate(
2 { email: 'tarou@example.com' },
3 { name: 'tarou' }
4);

the updateOrCreate method is also available.

deleting models

to delete a model, call the delete method on a model instance.

1const user = await User.find(1);
2await user.delete();

relationships

models can define relationships to other models, allowing you to fluently traverse and query connected data.

one to one

a one to one relationship is defined using the hasOne method. it requires the related model class and the foreign key name.

1import Profile from './Profile.js';
2
3export default class User extends Model {
4 profile() {
5 return this.hasOne(Profile, 'user_id');
6 }
7}

one to many

a one to many relationship is defined using the hasMany method.

1import Post from './Post.js';
2
3export default class User extends Model {
4 posts() {
5 return this.hasMany(Post, 'user_id');
6 }
7}

belongs to

the inverse of a hasOne or hasMany relationship is defined using the belongsTo method.

1import User from './User.js';
2
3export default class Post extends Model {
4 user() {
5 return this.belongsTo(User, 'user_id');
6 }
7}

once a relationship is defined, you can query it by calling the method, which returns a query builder.

1const activePosts = await user.posts().where('status', 'active').get();

eager loading

when you access a relationship as a property, the framework will read the preloaded relation data. to prevent the n+1 query problem, use the with method to eager load relationships when fetching the parent models.

1const users = await User.with('profile', 'posts').limit(10).get();
2
3for (const user of users) {
4 // accessing user.posts does not trigger an additional query
5 console.log(user.posts);
6}

you can eager load nested relationships using dot notation.

1const users = await User.with('posts.comments').get();

lazy eager loading

if you have already retrieved a model instance and need to eager load a relationship after the fact, use the load method.

1const user = await User.find(1);
2await user.load('posts', 'profile');

serialization

when you cast a model to a json string or return it from a route, the framework automatically converts it using the toJSON method. this method serializes all attributes and eager loaded relationships.

hiding attributes

you may wish to hide certain attributes, such as passwords or sensitive tokens, from the serialized output. define a static hidden array on your model to exclude these attributes.

1export default class User extends Model {
2 static hidden = ['password', 'secret', 'api_key'];
3}

by default, the framework hides password, token, secret, api_key, and remember_token.

mutations

models dynamically proxy property accesses to their underlying attribute store. you interact with the model instance as if it were a plain javascript object.

if you need to manually encrypt a sensitive field that isn't handled by the query builder's auto hashing configuration, you can use the hash method on the model instance.

1const user = await User.find(1);
2user.custom_secret = 'plain-text';
3await user.hash('custom_secret');
4await user.save();