Skip to content

Commit

Permalink
Merge pull request #159 from AthennaIO/develop
Browse files Browse the repository at this point in the history
improve docs
  • Loading branch information
jlenon7 committed Jan 6, 2024
2 parents b657c2f + 75b431f commit ae48ef7
Show file tree
Hide file tree
Showing 9 changed files with 866 additions and 16 deletions.
8 changes: 4 additions & 4 deletions docs/database/migrations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ timestamp that allows Athenna to determine the order of
the migrations:

```shell
node artisan make:migration create_flights_table
node artisan make:migration FlightsMigration
```

:::tip
Expand All @@ -48,7 +48,7 @@ expressively create and modify tables. For example, the following migration crea
```typescript
import { BaseMigration, type DatabaseImpl } from '@athenna/database'

export class CreateFlightsTable extends BaseMigration {
export class FlightsMigration extends BaseMigration {
public tableName = 'flights'

public async up(db: DatabaseImpl) {
Expand All @@ -75,7 +75,7 @@ you should set the static getter `connection` in your migration:
```typescript
import { BaseMigration, type DatabaseImpl } from '@athenna/database'

export class CreateFlightsTable extends BaseMigration {
export class FlightsMigration extends BaseMigration {
public static connection() {
return 'postgres'
}
Expand Down Expand Up @@ -108,7 +108,7 @@ node artisan migration:run --connection=postgres

:::warning

If `postgres` is your default connection than all the migrations
If `postgres` is your default connection then all the migrations
using the `default` value in the static `connection()` method
will run too.

Expand Down
169 changes: 168 additions & 1 deletion docs/orm/factories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,171 @@ See how to factory fake models in Athenna Framework.

## Introduction

Coming soon...
When testing your application or seeding your database, you may need to insert a
few records into your database. Instead of manually specifying the value of each
column, Athenna allows you to define a set of default attributes for each of your
models using model factories.

## Defining model factories

First thing you need to do before start using your factories is to define the
return value of the static `definition()` method of your model:

```typescript
import { BaseModel } from '@athenna/database'

export class User extends BaseModel {
@Column()
public id: number

@Column()
public name: string

@Column({ isUnique: true })
public email: string

@Column({ isCreateDate: true })
public createdAt: Date

@Column({ isUpdateDate: true })
public updatedAt: Date

@Column({ isDeleteDate: true })
public deletedAt: Date

public async static definition(): Promise<Partial<User>> {
return {
id: this.faker.number.int({ max: 100000 }),
name: this.faker.person.firstName(),
email: this.faker.internet.email(),
createdAt: this.faker.date.anytime(),
updatedAt: this.faker.date.anytime()
}
}
}
```

The definition method returns the default set of attribute values that should
be applied when creating a model using the factory.

Via the `faker` helper, models have access to the [`@faker-js/faker`](https://fakerjs.dev/),
which allows you to conveniently generate various kinds of random data for testing
and seeding.

## Creating models using factories

Once you have defined the `definition()` method of your model, you may use the
static factory method in order to instantiate a factory instance for that model.
Let's take a look at a few examples of creating models. First, we'll use the
`make()` method to create models without persisting them to the database:

```typescript
const user = await User.factory().make()
```

You may create a collection of many models using the `count()` method:

```typescript
const users = await User.factory()
.count(3)
.make()
```

### Creating trashed models

If your model is using [soft delete](/docs/orm/query-builder#soft-deleting)
approach you can apply the trashed state to your model by calling the `trashed()`
method:

```typescript
const users = await User.factory()
.count(3)
.trashed()
.make()
```

### Overriding attributes

If you would like to override some of the default values of your `definition()`
method, you may pass an array of values to the `make()` method. Only the
specified attributes will be replaced while the rest of the attributes remain
set to their default values as specified by the `definiton()` method:

```typescript
const user = await User.factory().make({ name: 'Thais Gabriela' })
```

### Returning specific fields

Sometimes you may need to return only a specific field from your fabricated data.
To do so, you can use the `returning()` method:

```typescript
// Make one User and return it email
const email = await User.factory()
.returning('email')
.make()

// Make three Users and return an array with three emails
const emails = await User.factory()
.count(3)
.returning('email')
.make()
```

## Persisting models

The `create()` method instantiates model instances and persists them to the
database:

```typescript
// Create a single User
const user = await User.factory().create()

// Create three Users
const users = await User.factory()
.count(3)
.create()
```

You may override the factory's default model attributes by passing an object
of attributes to the `create()` method:

```typescript
const user = await User.factory().create({ name: 'Thais' })
```

:::note

[Mass assignment protection](/docs/orm/query-builder#mass-assignment) is
automatically disabled when creating models using factories.

:::

## Defining relationships within factories

To define a relationship within your model factory, you will typically assign a
new factory instance to the foreign key of the relationship. This is normally
done for the "inverse" relationships such as `@BelongsTo()` relationships.

For example, if you would like to create a new user when creating a post,
you may use the `returningAs()` method that works exactly like the [`returning()`](/docs/orm/factories#returning-specific-fields)
method, the only difference is that `returningAs()` forces the TypeScript return type
to be the same of the field you are returning:

```typescript
import { User } from '#app/models/User'
import { BaseModel } from '@athenna/database'

export class Post extends Model {
public static definition(): Promise<Partial<Post>> {
return {
title: this.faker.commerce.productName(),
userId: User.factory().returningAs('id')
}
}

/*...*/
}
```

76 changes: 76 additions & 0 deletions docs/orm/query-builder.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ flight.name = 'Brazil to Ukraine'
await flight.save()
```

:::tip

To validate if a model is persisted in database you can use the
`isPersisted()` method:

```typescript
const flight = new Flight()

flight.name = 'Brazil to Ukraine'

if (!flight.isPersisted()) {
await flight.save()
}
```

:::

In this example, we assign the `name` field to the name attribute of
the `#app/models/Flight` model instance. When we call the `save()`
method, a record will be inserted into the database. The model's
Expand Down Expand Up @@ -285,6 +302,38 @@ flight.name = 'Paris to London'
await flight.save()
```

:::tip

To validate if some change has been done in the model after it was
retrieved from database you may use the `isDirty()` method:

```typescript
const flight = await Flight.query()
.where({ id: 1 })
.find()

if (!flight.isDirty()) {
flight.name = 'Paris to London'

await flight.save()
}
```

Also to get only the values that were modified you may use the `dirty()`
method:

```typescript
const flight = await Flight.query()
.where({ id: 1 })
.find()

flight.name = 'Paris to London'

const { name } = flight.dirty()
```

:::

#### Mass updates

Updates can also be performed against models that match a given query.
Expand Down Expand Up @@ -419,6 +468,33 @@ await Flight.query()
})
```

## Refreshing models

If you already have an instance of an model that was retrieved from the
database, you can "refresh" the model using the `fresh()` and `refresh()`
methods. The `fresh()` method will re-retrieve the model from the database.
The existing model instance will not be affected:

```typescript
const flight = await Flight.find({ number: 'FR 900' })

const freshFlight = await flight.fresh()
```

The `refresh()` method will re-hydrate the existing model using fresh data
from the database. In addition, all of its loaded relationships will be
refreshed as well:

```typescript
const flight = await Flight.find({ number: 'FR 900' })

flight.number = 'FR 456'

await flight.refresh()

console.log(flight.number) // "FR 900" 👈
```

## Deleting models

To delete a model, you may call the `delete()` method on the model
Expand Down
Loading

0 comments on commit ae48ef7

Please sign in to comment.