如何在 AdonisJS 5 中使用单个验证器 class 进行资源创建和修改?

How to use single validator class for both resource creation and modification in AdonisJS 5?

有时我可能需要使用单个验证器 class 来插入和更新资源,而不是 this statement。否则我可能会有重复的代码,这本质上违反了 DRY 原则。

考虑以下情况: 比如说,我的应用程序中有一个 products 资源,我的应用程序的用户可以创建、更新和删除产品。假设产品模型看起来像这样:

export default class Product extends BaseModel {
  @column({ isPrimary: true })
  public id: number
  @column()
  public code: string
  @column()
  public title: string
  @column()
  public description: string
  @column()
  public price: number
}

当然,迁移将非常接近于以下内容:

export default class ProductsSchema extends BaseSchema {
  protected tableName = 'products'
  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id').primary()
      table.string('code').unique().notNullable() // <= pay attention that this field is unique
      table.string('title').notNullable()
      table.string('description', 25).notNullable()
      table.double('price').notNullable()
    })
  }
  public async down() {
    this.schema.dropTable(this.tableName)
  }
}

现在用户将创建一个新产品。所以他们会看到一个表格,验证可能看起来像:

export default class ProductCreateValidator {
  constructor(protected ctx: HttpContextContract) {}
  public schema = schema.create({
    code: schema.string({ trim: true, escape: true }, [
      rules.unique({ table: 'products', column: 'code' }),
    ]), // <= because this field is unique inside the database
    title: schema.string({ trim: true, escape: true }, [
      rules.alpha({ allow: ['space'] }),
    ]),
    description: schema.string({ trim: true, escape: true }),
    price: schema.number(),
  })
  public cacheKey = this.ctx.routeKey
  public messages = {}
}

乐趣现在开始!如果我为更新产品创建单独的 class,除了 code 之外,大多数字段都是相同的。所以我必须复制整个 class:

export default class ProductUpdateValidator {
  constructor(protected ctx: HttpContextContract) {}
  public schema = schema.create({
    code: schema.string({ trim: true, escape: true }, [
      // rules.unique({ table: 'products', column: 'code' }),
    ]), // <= I cannot apply unique rule here - because I won't be able to update anymore
    title: schema.string({ trim: true, escape: true }, [
      rules.alpha({ allow: ['space'] }),
    ]),
    description: schema.string({ trim: true, escape: true }),
    price: schema.number(),
  })
  public cacheKey = this.ctx.routeKey
  public messages = {}
}

如果我想再添加3个字段怎么办?使用当前设置,我必须转到这两个 class 文件并在其中添加这些字段。如果我想调整一些验证逻辑,我将不得不访问这两个文件。如果我能够对创建和更新操作使用单个 class,那么维护起来会容易得多;如果用户尝试更新的产品的特定字段没有更改,它会自动取消唯一性检查。这怎么可能?

很容易实现。我们需要删除一个验证器 class 并像这样修改另一个:

export default class ProductValidator {
  constructor(protected ctx: HttpContextContract) {}
  public schema = schema.create({
    code: schema.string({ trim: true, escape: true }, [
      rules.unique({
        table: 'products',
        column: 'code',
        whereNot: {
          id: this.ctx.request.input('id') || 0 // <= or this may come from route params: this.ctx.params.id
        }
      }),
    ]),
    title: schema.string({ trim: true, escape: true }, [
      rules.alpha({ allow: ['space'] }),
    ]),
    description: schema.string({ trim: true, escape: true }),
    price: schema.number(),
  })
  public cacheKey = this.ctx.routeKey
  public messages = {}
}

让我们分解这段代码:

  • 对于插入新产品,this.ctx.request.input('id') 将为 undefined,因此它将回退到 0。所以它会用 ['<whatever_user_types>', 0] 执行 SELECT code FROM products WHERE code = ? AND NOT id = ? 查询。因为 id 是那个 table 的主键,它不能是 0,所以上面查询的后面的条件总是 TRUE。因此验证器只会在找到 code 时抛出错误(因为后面的部分已经是 TRUE)。这样我们的objective就圆满了。
  • 要更新现有产品,您一定会掌握产品的 ID。因为您是从数据库中获取产品,所以您当然知道它的 ID。现在把它放在你选择的某个地方(在更新表单中作为 <input type="hidden" name="id" value="{{ product.id }}"> 或作为路由参数 /products/:id/update)。由于这次我们有 ID,因此将设置 this.ctx.request.input('id')(或 this.ctx.params.id)。所以查询看起来像 SELECT code FROM products WHERE code = ? AND NOT id = ? 查询 ['<whatever_user_types>', <product_id>]。这次,查询的后面条件将始终为 FALSE,因此如果 code 仅与我们尝试更新的产品匹配而不匹配,它不会抱怨与任何其他产品。宾果!

这就是您可以通过对创建和更新操作使用单个验证器来避免代码重复的方法。如果您有任何其他问题,请在评论中告诉我。