Naming Strategies - 命名策略
underscored
参数
Sequelize 为模型提供了 underscored
参数. 设为 true
时,此参数会将所有属性的 field
参数设置为其名称的 snake_case 版本. 这也适用于由关联和其他自动生成的字段自动生成的外键. 例:
const User = sequelize.define('task', { username: Sequelize.STRING }, {
underscored: true
});
const Task = sequelize.define('task', { title: Sequelize.STRING }, {
underscored: true
});
User.hasMany(Task);
Task.belongsTo(User);
上面我们有模型 User 和 Task,都使用了 underscored
的参数. 他们之间也有一对多的关系. 另外,回想一下,由于默认情况下 timestamps
为 true
,因此我们应该期望 createedAt
和 updatedAt
字段也将自动创建.
如果没有 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()
将会生成以下内容:
CREATE TABLE IF NOT EXISTS "users" (
"id" SERIAL,
"username" VARCHAR(255),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "tasks" (
"id" SERIAL,
"title" VARCHAR(255),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL,
"user_id" INTEGER REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);
单数与复数
乍看之下,在 Sequelize 中是否应使用名称的单数形式或复数形式可能会造成混淆. 本节旨在澄清这一点.
回想一下 Sequelize 在后台使用了一个名为 inflection 的库,以便正确计算不规则的复数形式(例如 person -> people
). 但是,如果你使用的是另一种语言,则可能需要直接定义名称的单数和复数形式. sequelize 允许你通过一些参数来执行此操作.
定义模型时
模型应以单词的单数形式定义. 例:
sequelize.define('foo', { name: DataTypes.STRING });
上面的模型名称是 foo
(单数),表名称是 foos
,因为 Sequelize 会自动获取表名称的复数形式.
在模型中定义参考键时
sequelize.define('foo', {
name: DataTypes.STRING,
barId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "bars",
key: "id"
},
onDelete: "CASCADE"
},
});
在上面的示例中,我们手动定义了引用另一个模型的键. 这不是通常的做法,但是如果必须这样做,则应在此使用表名. 这是因为引用是根据引用的表名创建的. 在上面的示例中,使用了复数形式(bars
),假设 bar
模型是使用默认设置创建的(使其基础表自动复数).
从预先加载中检索数据时
当你在查询中执行 include
时,包含的数据将根据以下规则添加到返回对象的额外字段中:
- 当包含来自单个关联(
hasOne
或belongsTo
)的内容时,字段名称将是模型名称的单数形式; - 当包含来自多个关联(
hasMany
或belongsToMany
)的内容时,字段名称将是模型的复数形式.
简而言之,在每种情况下,字段名称将采用最合乎逻辑的形式.
示例:
// 假设 Foo.hasMany(Bar)
const foo = Foo.findOne({ include: Bar });
// foo.bars 将是一个数组
// foo.bar 将不存在,因为它没有意义
// 假设 Foo.hasOne(Bar)
const foo = Foo.findOne({ include: Bar });
// foo.bar 将是一个对象(如果没有关联的模型,则可能为 null)
// foo.bars 将不存在,因为它没有意义
// 等等.
定义别名时覆盖单数和复数
在为关联定义别名时,你可以传递一个对象以指定单数和复数形式,而不仅仅是使用 { as: 'myAlias' }
.
Project.belongsToMany(User, {
as: {
singular: 'líder',
plural: 'líderes'
}
});
如果你知道模型在关联中将始终使用相同的别名,则可以将单数和复数形式直接提供给模型本身:
const User = sequelize.define('user', { /* ... */ }, {
name: {
singular: 'líder',
plural: 'líderes',
}
});
Project.belongsToMany(User);
添加到用户实例的混入文件将使用正确的形式. 例如,Sequelize 将代替 project.addUser()
来提供 project.getLíder()
. 另外,Sequelize 将代替 project.setUsers()
来提供 project.setLíderes()
.
注意:记得使用 as
来改变关联的名字也会改变外键的名字. 因此,建议也指定在这种情况下直接涉及的外键.
// 错误示例
Invoice.belongsTo(Subscription, { as: 'TheSubscription' });
Subscription.hasMany(Invoice);
上面的第一个调用将在 Invoice
上建立一个名为 theSubscriptionId
的外键. 但是,第二个调用也会在 Invoice
上建立外键(因为众所周知, hasMany
调用会将外键放置在目标模型中)-但是,它将被命名为 subscriptionId
. 这样,你将同时具有 subscriptionId
和 theSubscriptionId
列.
最好的方法是为外键选择一个名称,并将其显式放置在两个调用中. 例如,如果选择了 subscription_id
:
// 修正示例
Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' });
Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' });