Naming Strategies - 命名策略

underscored 参数

Sequelize 为模型提供了 underscored 参数. 设为 true 时,此参数会将所有属性的 field 参数设置为其名称的 snake_case 版本. 这也适用于由关联和其他自动生成的字段自动生成的外键. 例:

  1. const User = sequelize.define('user', { username: Sequelize.STRING }, {
  2. underscored: true
  3. });
  4. const Task = sequelize.define('task', { title: Sequelize.STRING }, {
  5. underscored: true
  6. });
  7. User.hasMany(Task);
  8. Task.belongsTo(User);

上面我们有模型 User 和 Task,都使用了 underscored 的参数. 他们之间也有一对多的关系. 另外,回想一下,由于默认情况下 timestampstrue,因此我们应该期望 createedAtupdatedAt 字段也将自动创建.

如果没有 underscored 参数,Sequelize 会自动定义:

  • 每个模型的 createdAt 属性,指向每个表中名为 createdAt 的列
  • 每个模型的 updatedAt 属性,指向每个表中名为 updatedAt 的列
  • Task 模型中的 userId 属性,指向任务表中名为 userId 的列

启用 underscored 参数后,Sequelize 将改为定义:

  • 每个模型的 createdAt 属性,指向每个表中名为 created_at 的列
  • 每个模型的 updatedAt 属性,指向每个表中名为 updated_at 的列
  • Task 模型中的 userId 属性,指向任务表中名为 user_id 的列

请注意,在这两种情况下,JavaScript 字段均仍为 camelCase; 此参数仅更改这些字段如何映射到数据库本身. 每个属性的 field 参数都设置为它们的 snake_case 版本,但属性本身仍为 camelCase.

这样,在上面的代码上调用 sync() 将会生成以下内容:

  1. CREATE TABLE IF NOT EXISTS "users" (
  2. "id" SERIAL,
  3. "username" VARCHAR(255),
  4. "created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  5. "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  6. PRIMARY KEY ("id")
  7. );
  8. CREATE TABLE IF NOT EXISTS "tasks" (
  9. "id" SERIAL,
  10. "title" VARCHAR(255),
  11. "created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  12. "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
  13. "user_id" INTEGER REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
  14. PRIMARY KEY ("id")
  15. );

单数与复数

乍看之下,在 Sequelize 中是否应使用名称的单数形式或复数形式可能会造成混淆. 本节旨在澄清这一点.

回想一下 Sequelize 在后台使用了一个名为 inflection 的库,以便正确计算不规则的复数形式(例如 person -> people). 但是,如果你使用的是另一种语言,则可能需要直接定义名称的单数和复数形式. sequelize 允许你通过一些参数来执行此操作.

定义模型时

模型应以单词的单数形式定义. 例:

  1. sequelize.define('foo', { name: DataTypes.STRING });

上面的模型名称是 foo(单数),表名称是 foos,因为 Sequelize 会自动获取表名称的复数形式.

在模型中定义参考键时

  1. sequelize.define('foo', {
  2. name: DataTypes.STRING,
  3. barId: {
  4. type: DataTypes.INTEGER,
  5. allowNull: false,
  6. references: {
  7. model: "bars",
  8. key: "id"
  9. },
  10. onDelete: "CASCADE"
  11. },
  12. });

在上面的示例中,我们手动定义了引用另一个模型的键. 这不是通常的做法,但是如果必须这样做,则应在此使用表名. 这是因为引用是根据引用的表名创建的. 在上面的示例中,使用了复数形式(bars),假设 bar 模型是使用默认设置创建的(使其基础表自动复数).

从预先加载中检索数据时

当你在查询中执行 include 时,包含的数据将根据以下规则添加到返回对象的额外字段中:

  • 当包含来自单个关联(hasOnebelongsTo)的内容时,字段名称将是模型名称的单数形式;
  • 当包含来自多个关联(hasManybelongsToMany)的内容时,字段名称将是模型的复数形式.

简而言之,在每种情况下,字段名称将采用最合乎逻辑的形式.

示例:

  1. // 假设 Foo.hasMany(Bar)
  2. const foo = Foo.findOne({ include: Bar });
  3. // foo.bars 将是一个数组
  4. // foo.bar 将不存在,因为它没有意义
  5. // 假设 Foo.hasOne(Bar)
  6. const foo = Foo.findOne({ include: Bar });
  7. // foo.bar 将是一个对象(如果没有关联的模型,则可能为 null)
  8. // foo.bars 将不存在,因为它没有意义
  9. // 等等.

定义别名时覆盖单数和复数

在为关联定义别名时,你可以传递一个对象以指定单数和复数形式,而不仅仅是使用 { as: 'myAlias' }.

  1. Project.belongsToMany(User, {
  2. as: {
  3. singular: 'líder',
  4. plural: 'líderes'
  5. }
  6. });

如果你知道模型在关联中将始终使用相同的别名,则可以将单数和复数形式直接提供给模型本身:

  1. const User = sequelize.define('user', { /* ... */ }, {
  2. name: {
  3. singular: 'líder',
  4. plural: 'líderes',
  5. }
  6. });
  7. Project.belongsToMany(User);

添加到用户实例的混入文件将使用正确的形式. 例如,Sequelize 将代替 project.addUser() 来提供 project.getLíder(). 另外,Sequelize 将代替 project.setUsers() 来提供 project.setLíderes().

注意:记得使用 as 来改变关联的名字也会改变外键的名字. 因此,建议也指定在这种情况下直接涉及的外键.

  1. // 错误示例
  2. Invoice.belongsTo(Subscription, { as: 'TheSubscription' });
  3. Subscription.hasMany(Invoice);

上面的第一个调用将在 Invoice 上建立一个名为 theSubscriptionId 的外键. 但是,第二个调用也会在 Invoice 上建立外键(因为众所周知, hasMany 调用会将外键放置在目标模型中)-但是,它将被命名为 subscriptionId. 这样,你将同时具有 subscriptionIdtheSubscriptionId 列.

最好的方法是为外键选择一个名称,并将其显式放置在两个调用中. 例如,如果选择了 subscription_id

  1. // 修正示例
  2. Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' });
  3. Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' });