Lucid - SQL ORM

本指南介绍 Lucid ORM,即 AdonisJS 官方的数据库 ORM。你将学习如何:

  • 配置数据库连接
  • 使用查询构建器和模型
  • 创建迁移并定义关联关系
  • 使用事务和钩子
  • 序列化模型并生成测试数据

概述

Lucid ORM 是一个构建在 Knex 之上的 Active Record 风格 ORM,深度集成于 AdonisJS 生态系统中。与需要大量配置的独立 ORM 不同,Lucid 可以与 AdonisJS 的验证器、认证层、缓存、限流和队列等特性无缝协作,而无需任何额外设置。

Lucid 通过用特定的语言和类来封装常见操作,简化了数据库交互。它构建于 Knex 之上,这意味着你在需要时可以借助 JavaScript API 表达复杂的 SQL 查询。Lucid 支持多种数据库,包括 MySQL、PostgreSQL、Turso、SQLite 和 MSSQL。基于类的模型系统让你的代码直观且类型安全,而内建的关联关系支持让你可以建模复杂的数据结构。迁移系统为你的数据库模式提供了版本控制,而 seeders 和 factories 帮助你用测试数据填充数据库。

本指南对 Lucid 的特性进行高层概览,帮助你理解有哪些可用功能以及各部分如何组合。有关详细的 API 参考、高级模式以及特定功能的完整文档,请参阅 Lucid 官方文档

配置

Lucid 的配置位于 AdonisJS 项目根目录的 config/database.ts 文件中。该文件定义了你的数据库连接、迁移路径以及其他 ORM 设置。

config/database.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/lucid'

const dbConfig = defineConfig({
  connection: env.get('DB_CONNECTION'),
  connections: {
    postgres: {
      client: 'pg',
      connection: {
        host: env.get('DB_HOST'),
        port: env.get('DB_PORT'),
        user: env.get('DB_USER'),
        password: env.get('DB_PASSWORD'),
        database: env.get('DB_DATABASE'),
      },
      migrations: {
        naturalSort: true,
        paths: ['database/migrations'],
      },
    },
  },
})

export default dbConfig

配置指定了默认使用哪个数据库连接(通常通过环境变量设置),并定义了每个数据库的连接细节。每个连接都包含客户端库(如 PostgreSQL 的 pg 或 MySQL 的 mysql2)、连接凭据以及迁移文件路径。

你可以在 Lucid 配置文档 中探索所有可用的配置选项、连接池设置以及读写副本等高级特性。

直接使用查询构建器

在深入了解模型之前,你可以直接使用 Lucid 的查询构建器来进行数据库操作。查询构建器提供了一个流畅的 JavaScript API 来构造 SQL 查询,这对于复杂查询或不需要完整 Active Record 模式时特别有用。

查询构建器通过 db 服务可用,并且由于 Lucid 构建于 Knex 之上,它的工作方式与 Knex 完全相同。

app/controllers/posts_controller.ts
import db from '@adonisjs/lucid/services/db'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async index({ response }: HttpContext) {
    /**
     * 选择所有已发布的文章并按创建日期排序。
     * 这会返回一个普通对象数组。
     */
    const posts = await db
      .from('posts')
      .select('*')
      .where('status', 'published')
      .orderBy('created_at', 'desc')

    return response.json(posts)
  }

  async store({ request, response }: HttpContext) {
    const { title, content } = request.only(['title', 'content'])

    /**
     * 插入一篇新文章并返回生成的 ID。
     * 插入查询返回的是一个 ID 数组。
     */
    const [id] = await db
      .insertQuery()
      .table('posts')
      .insert({
        title,
        content,
        status: 'draft',
        created_at: new Date(),
        updated_at: new Date(),
      })

    return response.created({ id })
  }
}

查询构建器会自动处理参数化查询,防止 SQL 注入。你可以将其用于查询、插入、更新、删除、连接(join)、聚合以及任何其他 SQL 操作。当你需要原生 SQL 来处理复杂操作时,可以使用 db.rawQuery()

有关完整的查询构建器 API,包括连接、子查询、聚合和高级 where 子句,请参阅 Lucid 查询构建器文档

使用模型

模型提供了一种面向对象的方式来与数据库表交互。每个模型类代表一个表,每个模型实例代表一行。Lucid 采用以迁移为先的方法(migrations-first),你在迁移中定义模式,Lucid 会自动生成你的模型所继承的 TypeScript schema 类。

创建你的第一个迁移

迁移是 Lucid 模式管理的基础。它们为你的数据库模式提供了版本控制,使你可以随着时间推移逐步演进模式。每个迁移都是一个带有 updown 方法的 TypeScript 文件,分别定义如何将模式向前推进以及如何回滚。

为 posts 表创建一个迁移:

node ace make:migration posts

这会在 database/migrations/ 中生成一个带时间戳的迁移文件。时间戳确保迁移以正确的顺序运行。

database/migrations/1705234567890_create_posts_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'posts'

  async up() {
    /**
     * up 方法创建表结构。
     * 使用 schema 构建器来定义列和约束。
     */
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('title').notNullable()
      table.text('content').notNullable()
      table.string('status').defaultTo('draft')
      table.timestamp('created_at')
      table.timestamp('updated_at')
    })
  }

  async down() {
    /**
     * down 方法撤销 up 方法的更改。
     * 这使得在需要时可以回滚迁移。
     */
    this.schema.dropTable(this.tableName)
  }
}

运行迁移来创建表:

node ace migration:run

Lucid 执行迁移,在你的数据库中创建 posts 表,并自动在 database/schema.ts 生成一个包含所有类型安全列定义的 schema 类。

Tip

默认情况下,迁移在事务内部运行。如果迁移失败,所有更改都会自动回滚,使你的数据库保持一致状态。

有关更多迁移操作,如修改表、添加索引和使用外键,请参阅 Lucid 迁移文档

自动生成的 schema 类

运行迁移后,Lucid 会扫描你的数据库并生成 TypeScript schema 类,其中包含带有适当类型的所有列定义。这种以迁移为先的方法使你的模型保持简洁,并确保你的 TypeScript 类型始终与实际数据库模式匹配。

生成的 schema 类位于 database/schema.ts

database/schema.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'

export class PostsSchema extends BaseModel {
  static table = 'posts'

  @column({ isPrimary: true })
  declare id: number

  @column()
  declare title: string

  @column()
  declare content: string

  @column()
  declare status: string

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime | null

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime | null
}

Lucid 会自动将数据库类型转换为适当的 TypeScript 类型,将 snake_case 列名转换为 camelCase 属性,并将时间戳列转换为 Luxon DateTime 对象。autoCreate 选项意味着 Lucid 在创建记录时设置时间戳,autoUpdate 意味着它会在每次保存时更新时间戳。

Schema 生成规则

可以通过配置 schema 生成规则来控制默认的 schema 生成。请确保你的数据库配置启用了生成,并指向 database/schema_rules.ts 文件:

database/schema_rules.ts
import { type SchemaRules } from '@adonisjs/lucid/types/schema_generator';

export default {
  types: {
    /**
     * 全局将所有 JSON 列自定义为使用类型安全的 JSON 包装器,
     * 而不是默认的 'any' 类型。
     */
    jsonb: {
      decorator: '@column()',
      tsType: 'JSON<any>',
      imports: [{ source: '#types/db', typeImports: ['JSON'] }],
    },
  },
  tables: {
    /**
     * 自定义 users 表,使 user_role 列
     * 成为一个严格的联合类型,而不是通用的 string。
     */
    users: {
      columns: {
        user_role: {
          decorators: [{ name: '@column' }],
          tsType: `'admin' | 'editor'`,
        },
      },
    },
  },
} satisfies SchemaRules;

创建模型

现在创建一个继承生成的 schema 类的模型:

node ace make:model Post
app/models/post.ts
import { PostsSchema } from '#database/schema'

export default class Post extends PostsSchema {
  // 你的模型已准备就绪,可使用从 schema 继承的所有列
}

模型从 schema 类继承所有列定义。你会在这里添加关联关系、钩子和自定义方法,而 schema 类负责处理列定义。

Warning

切勿直接修改 database/schema.ts 中生成的 schema 类。这些文件在每次迁移后都会重新生成。相反,应该在继承 schema 类并保留你所做更改的模型类中覆盖列或添加自定义逻辑。

如果你需要更改列类型或添加列,请创建一个新的迁移。运行迁移后,schema 类会自动更新。

要了解有关自定义类型映射和 schema 生成规则的更多信息,请参阅 Lucid schema 类文档

基础 CRUD 操作

模型准备就绪后,你可以使用直观的 API 执行创建、读取、更新和删除操作。

创建记录:

app/controllers/posts_controller.ts
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async store({ request, response }: HttpContext) {
    /**
     * 使用 create 方法创建一篇新文章。
     * Lucid 会自动设置 created_at 和 updated_at。
     */
    const post = await Post.create({
      title: request.input('title'),
      content: request.input('content'),
      status: 'draft',
    })

    return response.created(post)
  }
}

读取记录:

app/controllers/posts_controller.ts
export default class PostsController {
  async index({ response }: HttpContext) {
    /**
     * 获取所有文章并按创建日期排序。
     * 返回 Post 模型实例数组。
     */
    const posts = await Post.query().orderBy('created_at', 'desc')
    return response.json(posts)
  }

  async show({ params, response }: HttpContext) {
    /**
     * 根据 ID 查找特定文章。
     * 如果未找到则抛出 404 异常。
     */
    const post = await Post.findOrFail(params.id)
    return response.json(post)
  }
}

更新记录:

app/controllers/posts_controller.ts
export default class PostsController {
  async update({ params, request, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)

    /**
     * 合并更新并保存到数据库。
     * updated_at 时间戳会自动更新。
     */
    await post.merge({
      title: request.input('title'),
      content: request.input('content'),
    }).save()

    return response.json(post)
  }
}

删除记录:

app/controllers/posts_controller.ts
export default class PostsController {
  async destroy({ params, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)

    /**
     * 从数据库中删除该文章。
     * 这会触发模型上定义的任何删除钩子。
     */
    await post.delete()

    return response.noContent()
  }
}

有关 firstOrCreateupdateOrCreate 等幂等操作,以及 createMany 等批量操作,请参阅 Lucid CRUD 操作文档

从模型访问查询构建器

虽然基础 CRUD 方法能处理常见场景,但你经常需要更复杂的查询。每个模型都通过 query() 方法提供对查询构建器的访问,在仍与模型实例协作的同时,为你提供完全的 SQL 灵活性。

app/controllers/posts_controller.ts
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async published({ response }: HttpContext) {
    /**
     * 使用查询构建器构建复杂查询。
     * 结果仍然是 Post 模型实例。
     */
    const posts = await Post.query()
      .where('status', 'published')
      .whereNotNull('published_at')
      .where('created_at', '>', new Date('2024-01-01'))
      .orderBy('published_at', 'desc')
      .limit(10)

    return response.json(posts)
  }

  async search({ request, response }: HttpContext) {
    const searchTerm = request.input('q')

    /**
     * 将带有操作符和多个条件的 where 子句结合使用。
     * orWhere 方法向查询添加 OR 条件。
     */
    const posts = await Post.query()
      .where('title', 'ilike', `%${searchTerm}%`)
      .orWhere('content', 'ilike', `%${searchTerm}%`)
      .where('status', 'published')

    return response.json(posts)
  }
}

查询构建器方法返回的是模型实例而非普通对象,这意味着你获得所有模型功能,如关联关系、序列化和钩子。你可以链式调用任何 Knex 查询构建器方法,包括连接、子查询、分组和聚合。

有关完整的查询构建器 API,请参阅 Lucid 查询构建器文档

在开发期间美化打印查询

了解你的应用生成了哪些 SQL 查询有助于调试和优化。Lucid 提供了内建的查询调试功能,可在开发期间将 SQL 语句美化打印到你的控制台。

美化打印在开发模式下默认启用。你会在终端中看到格式化的 SQL 查询:

select * from "posts" where "status" = 'published' order by "created_at" desc

该功能在数据库配置中配置:

config/database.ts
const dbConfig = defineConfig({
  prettyPrintDebugQueries: true,
  connections: {
    postgres: {
      client: 'pg',
      connection: {},
      /**
       * 启用调试模式以记录此连接的所有查询。
       * 在生产环境中设置为 false 以避免性能开销。
       */
      debug: true,
    },
  },
})

你也可以在每个查询的基础上启用调试:

const posts = await Post
  .query()
  .debug(true)
  .where('status', 'published')

有关对查询调试和日志记录(包括监听查询事件)的更多控制,请参阅 Lucid 调试文档

分页

分页通过以可管理的块加载记录,在处理大型数据集时防止性能问题。Lucid 提供了基于偏移量(offset-based)的分页,可与查询构建器和模型无缝集成。

app/controllers/posts_controller.ts
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async index({ request, response }: HttpContext) {
    const page = request.input('page', 1)
    const limit = 20

    /**
     * 每页分页加载 20 条文章。
     * 始终使用 orderBy 以确保分页的一致性。
     */
    const posts = await Post
      .query()
      .where('status', 'published')
      .orderBy('created_at', 'desc')
      .paginate(page, limit)

    /**
     * 设置分页链接的基础 URL。
     * 这样可以生成正确的页面 URL。
     */
    posts.baseUrl('/posts')

    return response.json(posts)
  }
}

分页器提供关于当前页面的元数据:

{
  "meta": {
    "total": 245,
    "perPage": 20,
    "currentPage": 1,
    "lastPage": 13,
    "firstPage": 1,
    "firstPageUrl": "/posts?page=1",
    "lastPageUrl": "/posts?page=13",
    "nextPageUrl": "/posts?page=2",
    "previousPageUrl": null
  },
  "data": [
    // Post 对象
  ]
}

你可以在模板中使用此元数据构建分页 UI:

@each(anchor in posts.getUrlsForRange(1, posts.lastPage))
  <a href="{{ anchor.url }}" class="{{ anchor.isActive ? 'active' : '' }}">
    {{ anchor.page }}
  </a>
@endeach
Tip

分页时始终包含 orderBy 子句。如果没有显式排序,数据库引擎可能会以随机顺序返回记录,导致用户在翻页时某些条目出现在多个页面上,或被完全跳过。

有关基于游标(cursor-based)的分页以及自定义 JSON 响应格式,请参阅 Lucid 分页文档

事务

数据库事务通过将多个操作分组为一个原子单元来确保数据完整性。如果任何操作失败,整个事务都会回滚,防止可能导致数据库处于不一致状态的局部更新。

Lucid 提供了托管事务(managed transactions),在成功时自动提交,在抛出异常时自动回滚:

app/controllers/posts_controller.ts
import db from '@adonisjs/lucid/services/db'
import Post from '#models/post'
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async store({ request, auth, response }: HttpContext) {
    /**
     * 将操作包裹在事务中。
     * 如果任何操作抛出异常,所有更改都会自动回滚。
     */
    const post = await db.transaction(async (trx) => {
      const user = auth.getUserOrFail()

      /**
       * 使用事务创建文章。
       * 此回调内的所有模型操作都使用同一事务。
       */
      const post = new Post()
      post.title = request.input('title')
      post.content = request.input('content')
      post.useTransaction(trx)
      await post.save()

      /**
       * 更新用户的文章计数。
       * 这共享同一事务,确保两个操作要么一起成功,要么一起失败。
       */
      user.useTransaction(trx)
      user.postCount = user.postCount + 1
      await user.save()

      return post
    })

    return response.created(post)
  }
}

有关手动事务控制、隔离级别和保存点(savepoints),请参阅 Lucid 事务文档

模型钩子

钩子允许你在模型生命周期的特定时刻执行代码。你可以使用钩子来在保存前对密码进行哈希处理、验证数据、更新关联记录,或执行任何应该在模型操作期间自动进行的逻辑。

app/models/user.ts
import { UsersSchema } from '#database/schema'
import { beforeSave } from '@adonisjs/lucid/orm'
import hash from '@adonisjs/core/services/hash'

export default class User extends UsersSchema {
  /**
   * 在保存到数据库之前对密码进行哈希处理。
   * 该钩子在插入和更新之前都会运行。
   */
  @beforeSave()
  static async hashPassword(user: User) {
    /**
     * 仅在密码被修改时对其进行哈希处理。
     * $dirty 对象跟踪哪些属性发生了更改。
     */
    if (user.$dirty.password) {
      user.password = await hash.make(user.password)
    }
  }
}

Lucid 为不同的生命周期事件提供钩子。前置钩子(@beforeSave@beforeCreate@beforeUpdate@beforeDelete)在数据库操作之前运行,可以修改数据或取消操作。后置钩子(@afterSave@afterCreate@afterUpdate@afterDelete)在数据库操作之后运行,对于发送通知等副作用很有用。查询钩子(@beforeFind@afterFind@beforeFetch@afterFetch)在获取操作期间运行,可以自动过滤结果。

app/models/post.ts
import { PostsSchema } from '#database/schema'
import { beforeFind, afterCreate } from '@adonisjs/lucid/orm'

export default class Post extends PostsSchema {
  /**
   * 自动从查询中排除软删除的文章。
   * 该钩子修改每个 find 查询以过滤已删除的记录。
   */
  @beforeFind()
  static ignoreDeleted(query) {
    query.where('isDeleted', false)
  }

  /**
   * 在创建文章后发送通知。
   * 后置钩子接收已保存的模型实例。
   */
  @afterCreate()
  static async notifyFollowers(post: Post) {
    // 向关注者发送通知
  }
}
Warning

直接的查询构建器更新会完全绕过钩子。当你使用 await Post.query().where('id', 1).update({ title: 'New' }) 时,不会执行任何钩子,时间戳也不会自动更新。

这种行为的存在是出于更新大量记录时的性能考虑。如果你需要运行钩子,请先获取模型实例,修改它,然后调用 save()

有关完整的钩子生命周期信息和高级模式,请参阅 Lucid 钩子文档

模型关联关系

关联关系定义了你的模型如何相互连接,使处理关联数据变得容易。Lucid 支持一对一、一对多和多对多关联,同时提供懒加载(lazy loading)和预加载(eager loading)。

定义关联关系

一个用户拥有多篇文章:

app/models/user.ts
import { UsersSchema } from '#database/schema'
import { hasMany } from '@adonisjs/lucid/orm'
import Post from '#models/post'
import type { HasMany } from '@adonisjs/lucid/types/relations'

export default class User extends UsersSchema {
  /**
   * 定义一对多关联关系。
   * 一个用户可以拥有多篇文章。
   */
  @hasMany(() => Post)
  declare posts: HasMany<typeof Post>
}

一篇文章属于一个用户:

app/models/post.ts
import { PostsSchema } from '#database/schema'
import { belongsTo } from '@adonisjs/lucid/orm'
import User from '#models/user'
import type { BelongsTo } from '@adonisjs/lucid/types/relations'

export default class Post extends PostsSchema {
  /**
   * 定义反向关联关系。
   * 每篇文章属于一个用户。
   */
  @belongsTo(() => User)
  declare user: BelongsTo<typeof User>
}

预加载关联关系

预加载会预先获取关联关系,避免了对每个关联记录执行一个查询的 N+1 查询问题:

app/controllers/posts_controller.ts
import Post from '#models/post'
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async index({ response }: HttpContext) {
    /**
     * 为所有文章预加载 user 关联关系。
     * 这总共执行两个查询:一个用于文章,一个用于所有用户。
     */
    const posts = await Post.query().preload('user')

    /**
     * 无需额外查询即可访问关联的用户。
     * 现在每篇文章都有一个带有已加载数据的 user 属性。
     */
    posts.forEach(post => {
      console.log(post.user.email)
    })

    return response.json(posts)
  }
}

你可以预加载多个关联关系并嵌套它们:

/**
 * 加载 user 及其 profile,以及所有带作者的 comments。
 * 嵌套预加载适用于任何深度的关联关系。
 */
const posts = await Post.query()
  .preload('user', (query) => {
    query.preload('profile')
  })
  .preload('comments', (query) => {
    query.preload('author')
  })

多对多关联关系

对于多对多关联,例如用户属于多个团队:

app/models/user.ts
import { UsersSchema } from '#database/schema'
import { manyToMany } from '@adonisjs/lucid/orm'
import Team from '#models/team'
import type { ManyToMany } from '@adonisjs/lucid/types/relations'

export default class User extends UsersSchema {
  /**
   * 通过数据透视表(pivot table)定义多对多关联关系。
   * Lucid 期望一个名为 team_user 的表,包含 user_id 和 team_id 列。
   */
  @manyToMany(() => Team, {
    pivotColumns: ['role', 'joined_at'],
  })
  declare teams: ManyToMany<typeof Team>
}

附加和分离关联记录:

const user = await User.findOrFail(1)

/**
 * 附加带有额外数据透视数据的团队。
 * 数据透视表存储关联关系以及额外的列。
 */
await user.related('teams').attach({
  1: { role: 'admin' },
  2: { role: 'member' },
})

/**
 * sync 只保留指定的团队,并分离其他的。
 * 对于"全部保存"风格的操作很有用。
 */
await user.related('teams').sync([1, 2, 3])

有关一对一关联、关联查询以及多态关联等高级模式,请参阅 Lucid 关联关系文档

序列化模型

当从 HTTP 端点返回模型时,你需要将它们转换为普通的 JavaScript 对象。Lucid 提供了强大的序列化功能,可以控制输出中出现哪些字段、转换数据以及处理关联关系。

app/controllers/users_controller.ts
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'

export default class UsersController {
  async show({ params, response }: HttpContext) {
    const user = await User.query()
      .where('id', params.id)
      .preload('posts')
      .firstOrFail()

    /**
     * 将模型序列化为普通对象。
     * 这会自动排除敏感字段并格式化日期。
     */
    return response.json(user.serialize({
      fields: {
        omit: ['password', 'rememberMeToken'],
      },
      relations: {
        posts: {
          fields: {
            pick: ['id', 'title', 'createdAt'],
          },
        },
      },
    }))
  }
}

在模型级别使用列选项控制序列化:

app/models/user.ts
import { UsersSchema } from '#database/schema'
import { column } from '@adonisjs/lucid/orm'

export default class User extends UsersSchema {
  /**
   * 覆盖 password 列以从所有序列化中排除。
   * 将 serializeAs 设置为 null 可防止此字段出现在 JSON 中。
   */
  @column({ serializeAs: null })
  declare password: string

  /**
   * 覆盖 firstName 列以在 JSON 输出中重命名。
   * 数据库列是 snake_case,但 JSON 使用 camelCase。
   */
  @column({ serializeAs: 'firstName' })
  declare firstName: string
}

有关自定义序列化逻辑、计算属性以及与 transformers 协作,请参阅 Lucid 序列化文档AdonisJS transformers 文档

模型工厂

工厂为测试和数据库填充生成假数据。你无需手动创建测试记录,只需定义一次工厂,即可按需生成逼真的数据。

为你的模型创建工厂:

node ace make:factory Post
database/factories/post_factory.ts
import Post from '#models/post'
import Factory from '@adonisjs/lucid/factories'

export const PostFactory = Factory.define(Post, ({ faker }) => {
  /**
   * 定义如何为每列生成假数据。
   * Faker 提供生成逼真假数据的方法。
   */
  return {
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(3),
    status: 'draft',
  }
}).build()

在测试或 seeders 中使用工厂:

import { PostFactory } from '#database/factories/post_factory'

/**
 * 创建一条带有假数据的文章。
 * 工厂为每个字段生成随机值。
 */
const post = await PostFactory.create()

/**
 * 一次性创建多篇文章。
 * 这会生成 10 篇带有唯一假数据的文章。
 */
const posts = await PostFactory.createMany(10)

/**
 * 在使用其他字段的假数据的同时,覆盖特定属性。
 * 当你需要特定值进行测试时很有用。
 */
const publishedPost = await PostFactory
  .merge({ status: 'published' })
  .create()

工厂支持用于常见变体的状态(states):

database/factories/post_factory.ts
export const PostFactory = Factory.define(Post, ({ faker }) => {
  return {
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(3),
    status: 'draft',
  }
})
  .state('published', (post) => {
    post.status = 'published'
    post.publishedAt = new Date()
  })
  .build()
/**
 * 使用状态创建已发布的文章。
 * 状态提供了工厂可复用的变体。
 */
const publishedPosts = await PostFactory
  .apply('published')
  .createMany(5)

有关关联工厂、在测试中桩化(stubbing)数据库调用以及高级工厂模式,请参阅 Lucid 工厂文档

下一步

本指南涵盖了 Lucid ORM 的基本特性,帮助你理解各部分如何组合。你已经学习了如何配置数据库连接、创建迁移、用自动生成的 schema 定义模型、执行 CRUD 操作、处理关联关系,以及使用工厂生成测试数据。

如需深入了解任何主题,请参阅全面的 Lucid 文档 ,其中涵盖了高级查询构建器方法、关联自定义、查询作用域(query scopes)、软删除、自定义命名策略等等。

你可能还想探索: