モデルの基本
このチュートリアルでは、Sequelizeにおけるモデルとは何か、そしてどのように使用するかを学びます。
概念
モデルはSequelizeの本質です。モデルは、データベース内のテーブルを表す抽象概念です。Sequelizeでは、Modelを拡張したクラスです。
モデルは、データベース内のテーブルの名前や、どのカラムを持っているか(そしてそれらのデータ型)など、それが表すエンティティについてSequelizeにいくつかのことを伝えます。
Sequelizeのモデルには名前があります。この名前は、データベース内で表すテーブルの名前と同じである必要はありません。通常、モデルは単数形の名前(例:User
)を持ち、テーブルは複数形の名前(例:Users
)を持ちますが、これは完全に設定可能です。
モデル定義
Sequelizeでは、モデルを2つの同等の方法で定義できます。
モデルが定義されると、モデル名で`sequelize.models`内で利用可能になります。
例を用いて学習するために、`firstName`と`lastName`を持つユーザーを表すモデルを作成したいとします。モデル名は`User`、データベース内のテーブル名は`Users`とします。
このモデルを定義する2つの方法を以下に示します。定義後、`sequelize.models.User`でモデルにアクセスできます。
`sequelize.define`を使用する:
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define(
'User',
{
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
// allowNull defaults to true
},
},
{
// Other model options go here
},
);
// `sequelize.define` also returns the model
console.log(User === sequelize.models.User); // true
Modelを拡張する
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
class User extends Model {}
User.init(
{
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
// allowNull defaults to true
},
},
{
// Other model options go here
sequelize, // We need to pass the connection instance
modelName: 'User', // We need to choose the model name
},
);
// the defined model is the class itself
console.log(User === sequelize.models.User); // true
内部的には、`sequelize.define`は`Model.init`を呼び出すため、どちらのアプローチも本質的に同等です。
パブリッククラスフィールドに関する注意点
モデルの属性と同じ名前のパブリッククラスフィールドを追加すると、問題が発生します。Sequelizeは、`Model.init`で定義された各属性にゲッターとセッターを追加します。パブリッククラスフィールドを追加すると、これらのゲッターとセッターがシャドウイングされ、モデルの実際のデータへのアクセスがブロックされます。
// Invalid
class User extends Model {
id; // this field will shadow sequelize's getter & setter. It should be removed.
otherPublicField; // this field does not shadow anything. It is fine.
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // undefined
// Valid
class User extends Model {
otherPublicField;
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // 1
TypeScriptでは、`declare`キーワードを使用することで、実際のパブリッククラスフィールドを追加せずに型情報を追加できます。
// Valid
class User extends Model {
declare id: number; // this is ok! The 'declare' keyword ensures this field will not be emitted by TypeScript.
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);
const user = new User({ id: 1 });
user.id; // 1
テーブル名の推測
上記のどちらの方法でも、テーブル名(`Users`)が明示的に定義されていないことに注意してください。ただし、モデル名(`User`)は指定されています。
デフォルトでは、テーブル名が指定されていない場合、Sequelizeは自動的にモデル名を複数形にしてテーブル名として使用します。この複数形化は、inflectionというライブラリによって内部的に行われるため、不規則な複数形(例:`person -> people`)も正しく計算されます。
もちろん、この動作は簡単に設定できます。
テーブル名をモデル名と同じにする
`freezeTableName: true`オプションを使用すると、Sequelizeによって実行される自動複数形化を停止できます。こうすることで、Sequelizeはテーブル名をモデル名と同じであると推測し、変更を加えません。
sequelize.define(
'User',
{
// ... (attributes)
},
{
freezeTableName: true,
},
);
上記の例では、`User`という名前のテーブルを指す`User`という名前のモデルが作成されます。
この動作は、sequelizeインスタンスの作成時にグローバルに定義することもできます。
const sequelize = new Sequelize('sqlite::memory:', {
define: {
freezeTableName: true,
},
});
こうすることで、すべてのテーブルでモデル名と同じ名前が使用されます。
テーブル名を直接指定する
テーブルの名前をSequelizeに直接伝えることもできます。
sequelize.define(
'User',
{
// ... (attributes)
},
{
tableName: 'Employees',
},
);
モデルの同期
モデルを定義すると、データベース内のテーブルについてSequelizeにいくつかのことを伝えています。しかし、テーブルがデータベースに存在しない場合はどうでしょうか?存在する場合でも、カラムが異なっていたり、少なかったり、その他の違いがある場合はどうでしょうか?
ここでモデルの同期が登場します。モデルは、非同期関数(Promiseを返す)である`model.sync(options)`を呼び出すことで、データベースと同期できます。この呼び出しにより、Sequelizeはデータベースに対してSQLクエリを自動的に実行します。これはデータベース内のテーブルのみを変更し、JavaScript側のモデルは変更しないことに注意してください。
- `User.sync()` - テーブルが存在しない場合は作成します(既に存在する場合は何もしません)。
- `User.sync({ force: true })` - テーブルを作成します。既に存在する場合は、最初に削除します。
- `User.sync({ alter: true })` - データベース内のテーブルの現在の状態(どのカラムを持っているか、データ型は何であるかなど)を確認し、テーブルがモデルと一致するように必要な変更を加えます。
例
await User.sync({ force: true });
console.log('The table for the User model was just (re)created!');
すべてのモデルを一度に同期する
`sequelize.sync()`を使用して、すべてのモデルを自動的に同期できます。例
await sequelize.sync({ force: true });
console.log('All models were synchronized successfully.');
テーブルの削除
モデルに関連付けられたテーブルを削除するには
await User.drop();
console.log('User table dropped!');
すべてのテーブルを削除するには
await sequelize.drop();
console.log('All tables dropped!');
データベースの安全確認
上記のように、`sync`操作と`drop`操作は破壊的です。Sequelizeは、追加の安全確認として`match`オプションを受け入れます。これはRegExpを受け取ります。
// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });
本番環境での同期
上記のように、`sync({ force: true })`と`sync({ alter: true })`は破壊的な操作になる可能性があります。そのため、本番レベルのソフトウェアにはお勧めしません。代わりに、マイグレーションの高度な概念を使用して、Sequelize CLIの助けを借りて同期を実行する必要があります。
タイムスタンプ
デフォルトでは、Sequelizeは`DataTypes.DATE`データ型を使用して、すべてのモデルに`createdAt`フィールドと`updatedAt`フィールドを自動的に追加します。これらのフィールドも自動的に管理されます。Sequelizeを使用して何かを作成または更新するたびに、これらのフィールドは正しく設定されます。 `createdAt`フィールドには作成日時を表すタイムスタンプが、`updatedAt`フィールドには最新の更新日時を表すタイムスタンプが含まれます。
**注:**これはSequelizeレベルで行われます(つまり、_SQLトリガー_では行われません)。これは、直接SQLクエリ(たとえば、Sequelizeを使用せずに他の方法で実行されたクエリ)では、これらのフィールドが自動的に更新されないことを意味します。
この動作は、`timestamps: false`オプションを使用してモデルに対して無効にすることができます。
sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
},
);
`createdAt` / `updatedAt`のいずれかのみを有効にし、これらのカラムにカスタム名を付けることもできます。
class Foo extends Model {}
Foo.init(
{
/* attributes */
},
{
sequelize,
// don't forget to enable timestamps!
timestamps: true,
// I don't want createdAt
createdAt: false,
// I want updatedAt to actually be called updateTimestamp
updatedAt: 'updateTimestamp',
},
);
列宣言の短縮構文
列について指定するのがデータ型だけの場合、構文を短縮できます。
// This:
sequelize.define('User', {
name: {
type: DataTypes.STRING,
},
});
// Can be simplified to:
sequelize.define('User', { name: DataTypes.STRING });
デフォルト値
デフォルトでは、Sequelize は列のデフォルト値を NULL
と見なします。この動作は、列定義に特定の defaultValue
を渡すことで変更できます。
sequelize.define('User', {
name: {
type: DataTypes.STRING,
defaultValue: 'John Doe',
},
});
DataTypes.NOW
など、いくつかの特別な値も受け入れられます。
sequelize.define('Foo', {
bar: {
type: DataTypes.DATETIME,
defaultValue: DataTypes.NOW,
// This way, the current date/time will be used to populate this column (at the moment of insertion)
},
});
データ型
モデルで定義するすべての列には、データ型が必要です。Sequelize は多くの組み込みデータ型を提供しています。組み込みデータ型にアクセスするには、DataTypes
をインポートする必要があります。
const { DataTypes } = require('sequelize'); // Import the built-in data types
文字列
DataTypes.STRING; // VARCHAR(255)
DataTypes.STRING(1234); // VARCHAR(1234)
DataTypes.STRING.BINARY; // VARCHAR BINARY
DataTypes.TEXT; // TEXT
DataTypes.TEXT('tiny'); // TINYTEXT
DataTypes.CITEXT; // CITEXT PostgreSQL and SQLite only.
DataTypes.TSVECTOR; // TSVECTOR PostgreSQL only.
ブール値
DataTypes.BOOLEAN; // TINYINT(1)
数値
DataTypes.INTEGER; // INTEGER
DataTypes.BIGINT; // BIGINT
DataTypes.BIGINT(11); // BIGINT(11)
DataTypes.FLOAT; // FLOAT
DataTypes.FLOAT(11); // FLOAT(11)
DataTypes.FLOAT(11, 10); // FLOAT(11,10)
DataTypes.REAL; // REAL PostgreSQL only.
DataTypes.REAL(11); // REAL(11) PostgreSQL only.
DataTypes.REAL(11, 12); // REAL(11,12) PostgreSQL only.
DataTypes.DOUBLE; // DOUBLE
DataTypes.DOUBLE(11); // DOUBLE(11)
DataTypes.DOUBLE(11, 10); // DOUBLE(11,10)
DataTypes.DECIMAL; // DECIMAL
DataTypes.DECIMAL(10, 2); // DECIMAL(10,2)
符号なし整数とゼロフィル整数 - MySQL/MariaDB のみ
MySQL と MariaDB では、データ型 INTEGER
、BIGINT
、FLOAT
、DOUBLE
を、次のように符号なしまたはゼロフィル(あるいは両方)として設定できます。
DataTypes.INTEGER.UNSIGNED;
DataTypes.INTEGER.ZEROFILL;
DataTypes.INTEGER.UNSIGNED.ZEROFILL;
// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER
// Same for BIGINT, FLOAT and DOUBLE
日付
DataTypes.DATE; // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
DataTypes.DATE(6); // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
DataTypes.DATEONLY; // DATE without time
UUID
UUID には、DataTypes.UUID
を使用します。PostgreSQL と SQLite では UUID
データ型になり、MySQL では CHAR(36)
になります。Sequelize はこれらのフィールドの UUID を自動的に生成できます。デフォルト値として DataTypes.UUIDV1
または DataTypes.UUIDV4
を使用するだけです。
{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1
}
その他
その他のデータ型については、別のガイドで説明しています。
列オプション
列を定義する際には、列の type
と、前述の allowNull
および defaultValue
オプションに加えて、他にも多くのオプションを使用できます。以下にいくつかの例を示します。
const { Model, DataTypes, Deferrable } = require('sequelize');
class Foo extends Model {}
Foo.init(
{
// instantiating will automatically set the flag to true if not set
flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
// default values for dates => current time
myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
// setting allowNull to false will add NOT NULL to the column, which means an error will be
// thrown from the DB when the query is executed if the column is null. If you want to check that a value
// is not null before querying the DB, look at the validations section below.
title: { type: DataTypes.STRING, allowNull: false },
// Creating two objects with the same value will throw an error. The unique property can be either a
// boolean, or a string. If you provide the same string for multiple columns, they will form a
// composite unique key.
uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },
// The unique property is simply a shorthand to create a unique constraint.
someUnique: { type: DataTypes.STRING, unique: true },
// Go on reading for further information about primary keys
identifier: { type: DataTypes.STRING, primaryKey: true },
// autoIncrement can be used to create auto_incrementing integer columns
incrementMe: { type: DataTypes.INTEGER, autoIncrement: true },
// You can specify a custom column name via the 'field' attribute:
fieldWithUnderscores: {
type: DataTypes.STRING,
field: 'field_with_underscores',
},
// It is possible to create foreign keys:
bar_id: {
type: DataTypes.INTEGER,
references: {
// This is a reference to another model
model: Bar,
// This is the column name of the referenced model
key: 'id',
// With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type.
deferrable: Deferrable.INITIALLY_IMMEDIATE,
// Options:
// - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints
// - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction
// - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction
},
},
// Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL
commentMe: {
type: DataTypes.INTEGER,
comment: 'This is a column name that has a comment',
},
},
{
sequelize,
modelName: 'foo',
// Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options:
indexes: [{ unique: true, fields: ['someUnique'] }],
},
);
モデルがクラスであることを活用する
Sequelize モデルは、ES6 クラスです。カスタムインスタンスメソッドまたはクラスレベルメソッドを非常に簡単に追加できます。
class User extends Model {
static classLevelMethod() {
return 'foo';
}
instanceLevelMethod() {
return 'bar';
}
getFullname() {
return [this.firstname, this.lastname].join(' ');
}
}
User.init(
{
firstname: Sequelize.TEXT,
lastname: Sequelize.TEXT,
},
{ sequelize },
);
console.log(User.classLevelMethod()); // 'foo'
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'