sequelize-patterns について
sequelize-patternsは、Sequelizeを使う開発者向けのスキルです。Node.jsのSQLデータベース用ORMについて、モデル、クエリ、マイグレーションなどの文書に基づく指針を提供します。
主な機能
- 01モデルと関連付けの指針
- 02マイグレーションとクエリのパターン
- 03トランザクション、検証、フック
- 04コネクションプールの設定
- 05命名とQuery Interfaceの例
活用シーン
- 01Sequelizeのモデルと関連付けの設計
- 02SQLデータベース向けマイグレーションの作成
- 03Sequelizeのクエリや設定のデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ joneqian/ claude-skills-suite --skill sequelize-patternsインストール後、Claude Code で/sequelize-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced.mdassociations.mddata_types.mddatabase.mddeployment.mdgetting_started.mdindex.mdmigrations.mdmodels.mdother.mdquerying.mdtypescript.md詳細
Sequelize スキル
SQL データベース向けの Sequelize Node.js ORM です。データベースモデル、マイグレーション、関連付け、クエリ、トランザクション、バリデーション、フック、および PostgreSQL、MySQL、MariaDB、SQLite、SQL Server での作業に使用します。公式ドキュメントから生成されています。
このスキルを使用する場面
このスキルは次の場合にトリガーされるべきです:
- Sequelize を使用する場合
- Sequelize の機能や API について質問する場合
- Sequelize のソリューションを実装する場合
- Sequelize のコードをデバッグする場合
- Sequelize のベストプラクティスを学ぶ場合
クイックリファレンス
一般的なパターン
パターン 1: コネクションプール 単一のプロセスからデータベースに接続する場合、Sequelize インスタンスは 1 つだけ作成する必要があります。Sequelize は初期化時にコネクションプールをセットアップします。このコネクションプールは、次の例に示すように、コンストラクターの options パラメーター(options.pool を使用)で設定できます: const sequelize = new Sequelize(/_ ... _/, { // ... pool: { max: 5, min: 0, acquire: 30000, idle: 10000 }}); 詳細については、Sequelize コンストラクターの API リファレンスを参照してください。複数のプロセスからデータベースに接続する場合、プロセスごとに 1 つのインスタンスを作成する必要がありますが、合計の最大サイズが守られるように、各インスタンスの最大コネクションプールサイズを設定する必要があります。たとえば、最大コネクションプールサイズを 90 にしたい場合で、3 つのプロセスがある場合、各プロセスの Sequelize インスタンスの最大コネクションプールサイズは 30 にすべきです。
options
パターン 2: 命名戦略
underscored オプション
Sequelize はモデルに対して underscored オプションを提供しています。これが true の場合、このオプションはすべての属性の field オプションを、その名前のスネークケース版に設定します。これは、関連によって自動生成される外部キーや、その他の自動生成されるフィールドにも適用されます。例:
const User = sequelize.define(
'user',
{
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 であるため、createdAt フィールドと updatedAt フィールドも自動的に作成されることを思い出してください。
underscored オプションがない場合、Sequelize は自動的に次を定義します:
- 各モデルの
createdAt属性。各テーブルのcreatedAtという名前のカラムを指します - 各モデルの
updatedAt属性。各テーブルのupdatedAtという名前のカラムを指します TaskモデルのuserId属性。taskテーブルのuserIdという名前のカラムを指します
underscored オプションが有効な場合、Sequelize は代わりに次を定義します:
- 各モデルの
createdAt属性。各テーブルのcreated_atという名前のカラムを指します - 各モデルの
updatedAt属性。各テーブルのupdated_atという名前のカラムを指します TaskモデルのuserId属性。taskテーブルのuser_idという名前のカラムを指します
どちらの場合でも、JavaScript 側ではフィールドは依然としてキャメルケースであることに注意してください。このオプションは、これらのフィールドがデータベース自体にどのようにマッピングされるかを変更するだけです。すべての属性の field オプションはスネークケース版に設定されますが、属性自体はキャメルケースのままです。
このように、上記のコードで 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',
},
});
上記の例では、別のモデルを参照するキーを手動で定義しています。これは通常行うことではありませんが、行う必要がある場合は、そこにテーブル名を使用する必要があります。これは、参照が参照先のテーブル名に基づいて作成されるためです。上記の例では、bar モデルがデフォルト設定で作成された(その基になるテーブルが自動的に複数形化される)と仮定して、複数形(bars)が使用されました。
Eager Loading でデータを取得するとき
クエリで 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);
user インスタンスに追加されるミックスインは、正しい形式を使用します。たとえば、project.addUser() の代わりに、Sequelize は project.getLíder() を提供します。また、project.setUsers() の代わりに、Sequelize は project.setLíderes() を提供します。
注: as を使用して関連の名前を変更すると、外部キーの名前も変更されることを思い出してください。したがって、この場合は関与する外部キーも直接指定することをお勧めします。
// 起こりうる間違いの例
Invoice.belongsTo(Subscription, { as: 'TheSubscription' });
Subscription.hasMany(Invoice);
上記の最初の呼び出しは、Invoice に theSubscriptionId という外部キーを確立します。ただし、2 番目の呼び出しも Invoice に外部キーを確立します(ご存知のとおり、hasMany の呼び出しはターゲットモデルに外部キーを配置します) - しかし、それは subscriptionId と名付けられます。このように、subscriptionId と theSubscriptionId の両方のカラムを持つことになります。最善のアプローチは、外部キーの名前を選択し、両方の呼び出しで明示的に配置することです。たとえば、subscription_id が選択された場合:
// 修正された例
Invoice.belongsTo(Subscription, {
as: 'TheSubscription',
foreignKey: 'subscription_id',
});
Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' });
underscored
パターン 3: モデルは単数形の単語で定義する必要があります。例:
sequelize.define('foo', { name: DataTypes.STRING });
パターン 4: デフォルトでは、null はモデルのすべてのカラムで許可される値です。これは、コード例の username フィールドで行ったように、カラムに allowNull: false オプションを設定することで無効にできます:
null
パターン 5: クエリインターフェイスSequelize のインスタンスは、クエリインターフェイスと呼ばれるものを使用して、方言に依存しない方法でデータベースと通信します。このマニュアルで学んだほとんどのメソッドは、クエリインターフェイスのいくつかのメソッドの助けを借りて実装されています。したがって、クエリインターフェイスのメソッドはより低レベルなメソッドです。Sequelize のより高レベルな API で実現する別の方法が見つからない場合にのみ、これらを使用してください。もちろん、これらは生のクエリを直接実行する (つまり、SQL を手で書く) よりも高レベルです。このガイドではいくつかの例を示しますが、実行できることの完全なリストと各メソッドの詳細な使用方法については、QueryInterface API を確認してください。クエリインターフェイスの取得 これ以降、Sequelize インスタンスで利用できる QueryInterface クラスのシングルトンインスタンスを queryInterface と呼びます: const { Sequelize, DataTypes } = require('sequelize');const sequelize = new Sequelize(/_ ... /);const queryInterface = sequelize.getQueryInterface(); テーブルの作成 queryInterface.createTable('Person', { name: DataTypes.STRING, isBetaMember: { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false, },}); 生成される SQL (SQLite を使用): CREATE TABLE IF NOT EXISTS Person ( name VARCHAR(255), isBetaMember TINYINT(1) NOT NULL DEFAULT 0); 注: 代わりにモデルを定義して YourModel.sync() を呼び出すことを検討してください。これはより高レベルなアプローチです。テーブルへのカラムの追加 queryInterface.addColumn('Person', 'petName', { type: DataTypes.STRING }); 生成される SQL (SQLite を使用): ALTER TABLE Person ADD petName VARCHAR(255); カラムのデータ型の変更 queryInterface.changeColumn('Person', 'foo', { type: DataTypes.FLOAT, defaultValue: 3.14, allowNull: false,}); 生成される SQL (MySQL を使用): ALTER TABLE Person CHANGE foo foo FLOAT NOT NULL DEFAULT 3.14; カラムの削除 queryInterface.removeColumn('Person', 'petName', { / query options _/}); 生成される SQL (PostgreSQL を使用): ALTER TABLE "public"."Person" DROP COLUMN "petName"; SQLite でのカラムの変更と削除 SQLite はカラムの直接的な変更と削除をサポートしていません。ただし、Sequelize はこれらの手順にヒントを得て、バックアップテーブルの助けを借りてテーブル全体を再作成することでこれを回避しようとします。例: // SQLite に次のように作成されたテーブルがあると仮定します:queryInterface.createTable('Person', { name: DataTypes.STRING, isBetaMember: { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false, }, petName: DataTypes.STRING, foo: DataTypes.INTEGER,});// そしてカラムを変更します:queryInterface.changeColumn('Person', 'foo', { type: DataTypes.FLOAT, defaultValue: 3.14, allowNull: false,}); SQLite では次の SQL 呼び出しが生成されます: PRAGMA TABLE_INFO(Person);CREATE TABLE IF NOT EXISTS Person_backup ( name VARCHAR(255), isBetaMember TINYINT(1) NOT NULL DEFAULT 0, foo FLOAT NOT NULL DEFAULT '3.14', petName VARCHAR(255));INSERT INTO Person_backup SELECT name, isBetaMember, foo, petName FROM Person;DROP TABLE Person;CREATE TABLE IF NOT EXISTS Person ( name VARCHAR(255), isBetaMember TINYINT(1) NOT NULL DEFAULT 0, foo FLOAT NOT NULL DEFAULT '3.14', petName VARCHAR(255));INSERT INTO Person SELECT name, isBetaMember, foo, petName FROM Person_backup;DROP TABLE Person_backup; その他 このガイドの冒頭で述べたように、Sequelize で利用できるクエリインターフェイスにはさらに多くの機能があります! 実行できることの完全なリストについては、QueryInterface API を確認してください。
queryInterface
パターン 6: 高度な M:N 関連このガイドを読む前に、関連のガイドを読んでいることを確認してください。まず、User と Profile の間の多対多リレーションシップの例から始めましょう。 const User = sequelize.define( 'user', { username: DataTypes.STRING, points: DataTypes.INTEGER, }, { timestamps: false },);const Profile = sequelize.define( 'profile', { name: DataTypes.STRING, }, { timestamps: false },); 多対多リレーションシップを定義する最も簡単な方法は次のとおりです。 User.belongsToMany(Profile, { through: 'User_Profiles' });Profile.belongsToMany(User, { through: 'User_Profiles' }); 上記のように through に文字列を渡すことで、Sequelize に対して、中間テーブル(ジャンクションテーブルとも呼ばれます)として User_Profiles という名前のモデルを自動生成するよう求めています。このモデルには userId と profileId の 2 つのカラムだけが含まれます。これらの 2 つのカラムには複合ユニークキーが設定されます。 中間テーブルとして使用するモデルを自分で定義することもできます。 const User_Profile = sequelize.define('User_Profile', {}, { timestamps: false });User.belongsToMany(Profile, { through: User_Profile });Profile.belongsToMany(User, { through: User_Profile }); 上記はまったく同じ効果を持ちます。User_Profile モデルに属性をまったく定義していないことに注意してください。これを belongsToMany の呼び出しに渡したという事実が、Sequelize に対して userId と profileId の 2 つの属性を自動的に作成するよう指示します。ちょうど他の関連でも、Sequelize が関係するモデルの一方にカラムを自動的に追加するのと同じです。 ただし、モデルを自分で定義することにはいくつかの利点があります。たとえば、中間テーブルにより多くのカラムを定義できます。 const User_Profile = sequelize.define( 'User_Profile', { selfGranted: DataTypes.BOOLEAN, }, { timestamps: false },);User.belongsToMany(Profile, { through: User_Profile });Profile.belongsToMany(User, { through: User_Profile }); これにより、中間テーブルで追加情報、すなわち selfGranted ブール値を追跡できるようになります。たとえば、user.addProfile() を呼び出すときに、through オプションを使用して追加カラムの値を渡すことができます。 例: const amidala = await User.create({ username: 'p4dm3', points: 1000 });const queen = await Profile.create({ name: 'Queen' });await amidala.addProfile(queen, { through: { selfGranted: false } });const result = await User.findOne({ where: { username: 'p4dm3' }, include: Profile,});console.log(result); 出力: { "id": 4, "username": "p4dm3", "points": 1000, "profiles": [ { "id": 6, "name": "queen", "User_Profile": { "userId": 4, "profileId": 6, "selfGranted": false } } ]} すべてのリレーションシップを 1 回の create 呼び出しで作成することもできます。 例: const amidala = await User.create( { username: 'p4dm3', points: 1000, profiles: [ { name: 'Queen', User_Profile: { selfGranted: true, }, }, ], }, { include: Profile, },);const result = await User.findOne({ where: { username: 'p4dm3' }, include: Profile,});console.log(result); 出力: { "id": 1, "username": "p4dm3", "points": 1000, "profiles": [ { "id": 1, "name": "Queen", "User_Profile": { "selfGranted": true, "userId": 1, "profileId": 1 } } ]} おそらくお気づきかもしれませんが、User_Profiles テーブルには id フィールドがありません。上記で述べたように、代わりに複合ユニークキーを持っています。この複合ユニークキーの名前は Sequelize によって自動的に選ばれますが、uniqueKey オプションでカスタマイズできます。 User.belongsToMany(Profile, { through: User_Profiles, uniqueKey: 'my_custom_unique',}); もう 1 つの可能性として、必要であれば、他の標準テーブルと同様に中間テーブルに主キーを強制的に持たせることもできます。これを行うには、単にモデルで主キーを定義します。 const User_Profile = sequelize.define( 'User_Profile', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, }, selfGranted: DataTypes.BOOLEAN, }, { timestamps: false },);User.belongsToMany(Profile, { through: User_Profile });Profile.belongsToMany(User, { through: User_Profile }); 上記はもちろん userId と profileId の 2 つのカラムを引き続き作成しますが、それらに複合ユニークキーを設定する代わりに、モデルは自身の id カラムを主キーとして使用します。それ以外はすべて問題なく動作します。 中間テーブルと通常のテーブル、そして「スーパー多対多関連」 ここでは、上記で示した最後の多対多の設定と、通常の一対多リレーションシップの使用法を比較し、最終的に「スーパー多対多リレーションシップ」の概念で結論づけます。 モデルの再確認(若干の名前変更あり) 理解しやすくするために、User_Profile モデルの名前を grant に変更しましょう。すべてが以前と同じように動作することに注意してください。モデルは次のとおりです。 const User = sequelize.define( 'user', { username: DataTypes.STRING, points: DataTypes.INTEGER, }, { timestamps: false },);const Profile = sequelize.define( 'profile', { name: DataTypes.STRING, }, { timestamps: false },);const Grant = sequelize.define( 'grant', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, }, selfGranted: DataTypes.BOOLEAN, }, { timestamps: false },); 中間テーブルとして Grant モデルを使用して、User と Profile の間に多対多リレーションシップを確立しました。 User.belongsToMany(Profile, { through: Grant });Profile.belongsToMany(User, { through: Grant }); これにより、userId と profileId のカラムが Grant モデルに自動的に追加されました。 注: 上記のように、grant モデルに単一の主キー(通常どおり id と呼ばれます)を強制的に持たせることを選択しました。これは、まもなく定義されるスーパー多対多リレーションシップに必要です。 代わりに一対多リレーションシップを使用する 上記で定義した多対多リレーションシップを設定する代わりに、次のようにしたらどうなるでしょうか? // User と Grant の間に一対多リレーションシップを設定するUser.hasMany(Grant);Grant.belongsTo(User);// 同様に Profile と Grant の間に一対多リレーションシップを設定するProfile.hasMany(Grant);Grant.belongsTo(Profile); 結果は本質的に同じです! これは、User.hasMany(Grant) と Profile.hasMany(Grant) がそれぞれ userId と profileId カラムを Grant に自動的に追加するためです。これは、1 つの多対多リレーションシップが 2 つの一対多リレーションシップとそれほど変わらないことを示しています。データベース内のテーブルは同じに見えます。唯一の違いは、Sequelize で Eager Loading を実行しようとするときです。 // 多対多アプローチでは、次のことができます:User.findAll({ include: Profile });Profile.findAll({ include: User });// ただし、次のことはできません:User.findAll({ include: Grant });Profile.findAll({ include: Grant });Grant.findAll({ include: User });Grant.findAll({ include: Profile });// 一方、二重一対多アプローチでは、次のことができます:User.findAll({ include: Grant });Profile.findAll({ include: Grant });Grant.findAll({ include: User });Grant.findAll({ include: Profile });// ただし、次のことはできません:User.findAll({ include: Profile });Profile.findAll({ include: User });// ただし、次のようにネストされた include でそれらをエミュレートできます:User.findAll({ include: { model: Grant, include: Profile, },}); // これは User.findAll({ include: Profile }) をエミュレートしますが、// 結果のオブジェクト構造は少し異なります。元の// 構造は user.profiles[].grant という形式ですが、エミュレートされた// 構造は user.grants[].profiles[] という形式です。 両方の長所: スーパー多対多リレーションシップ 上記で示した両方のアプローチを単純に組み合わせることができます! // スーパー多対多リレーションシップUser.belongsToMany(Profile, { through: Grant });Profile.belongsToMany(User, { through: Grant });User.hasMany(Grant);Grant.belongsTo(User);Profile.hasMany(Grant);Grant.belongsTo(Profile); このようにして、あらゆる種類の Eager Loading を実行できます。 // これらはすべて動作します:User.findAll({ include: Profile });Profile.findAll({ include: User });User.findAll({ include: Grant });Profile.findAll({ include: Grant });Grant.findAll({ include: User });Grant.findAll({ include: Profile }); あらゆる種類の深くネストされた include を実行することもできます。 User.findAll({ include: [ { model: Grant, include: [User, Profile], }, { model: Profile, include: { model: User, include: { model: Grant, include: [User, Profile], }, }, }, ],}); エイリアスとカスタムキー名 他のリレーションシップと同様に、多対多リレーションシップにもエイリアスを定義できます。続行する前に、関連のガイドにある belongsTo のエイリアスの例を思い出してください。その場合、関連を定義することが、include の実行方法(つまり、関連名を渡すこと)と、Sequelize が外部キーに選ぶ名前(その例では、leaderId が Ship モデルに作成されました)の両方に影響することに注意してください。 belongsToMany 関連にエイリアスを定義することも、include の実行方法に影響します。 Product.belongsToMany(Category, { as: 'groups', through: 'product_categories',});Category.belongsToMany(Product, { as: 'items', through: 'product_categories' });// [...]await Product.findAll({ include: Category }); // これは動作しませんawait Product.findAll({ // これは動作します、エイリアスを渡しています include: { model: Category, as: 'groups', },});await Product.findAll({ include: 'groups' }); // これも動作します ただし、ここでエイリアスを定義することは、外部キー名とは何の関係もありません。中間テーブルに作成される両方の外部キーの名前は、関連付けられるモデルの名前に基づいて Sequelize によって構築されます。これは、上記の例で中間テーブルに対して生成された SQL を調べることで簡単に確認できます。 CREATE TABLE IF NOT EXISTS product_categories ( createdAt DATETIME NOT NULL, updatedAt DATETIME NOT NULL, productId INTEGER NOT NULL REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE, categoryId INTEGER NOT NULL REFERENCES categories (id) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY (productId, categoryId)); 外部キーが productId と categoryId であることがわかります。これらの名前を変更するために、Sequelize はそれぞれ foreignKey と otherKey オプションを受け付けます(つまり、foreignKey は through リレーションのソースモデルのキーを定義し、otherKey はターゲットモデルのキーを定義します)。 Product.belongsToMany(Category, { through: 'product_categories', foreignKey: 'objectId', // productId を置き換えます otherKey: 'typeId', // categoryId を置き換えます});Category.belongsToMany(Product, { through: 'product_categories', foreignKey: 'typeId', // categoryId を置き換えます otherKey: 'objectId', // productId を置き換えます}); 生成される SQL: CREATE TABLE IF NOT EXISTS product_categories ( createdAt DATETIME NOT NULL, updatedAt DATETIME NOT NULL, objectId INTEGER NOT NULL REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE, typeId INTEGER NOT NULL REFERENCES categories (id) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY (objectId, typeId)); 上記のように、2 つの belongsToMany 呼び出しで多対多リレーションシップを定義する場合(これが標準的な方法です)、両方の呼び出しで foreignKey と otherKey オプションを適切に指定する必要があります。これらのオプションを片方の呼び出しにしか渡さない場合、Sequelize の動作は信頼できません。 自己参照 Sequelize は、直感的に、自己参照の多対多リレーションシップをサポートしています。 Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' });// これにより、オブジェクトの id を格納する PersonChildren テーブルが作成されます。 中間テーブルから属性を指定する デフォルトでは、多対多リレーションシップを Eager Loading するとき、Sequelize は次の構造(このガイドの最初の例に基づく)でデータを返します。 // User.findOne({ include: Profile }){ "id": 4, "username": "p4dm3", "points": 1000, "profiles": [ { "id": 6, "name": "queen", "grant": { "userId": 4, "profileId": 6, "selfGranted": false } } ]} 外側のオブジェクトが User であり、profiles というフィールドを持ち、それが Profile の配列であり、各 Profile には grant という追加フィールドが付属しており、それが Grant インスタンスであることに注意してください。これは、多対多リレーションシップから Eager Loading するときに Sequelize が作成するデフォルトの構造です。 ただし、中間テーブルの一部の属性だけが必要な場合は、attributes オプションで必要な属性の配列を指定できます。たとえば、中間テーブルから selfGranted 属性だけが必要な場合: User.findOne({ include: { model: Profile, through: { attributes: ['selfGranted'], }, },}); 出力: { "id": 4, "username": "p4dm3", "points": 1000, "profiles": [ { "id": 6, "name": "queen", "grant": { "selfGranted": false } } ]} ネストされた grant フィールドがまったく不要な場合は、attributes: [] を使用します。 User.findOne({ include: { model: Profile, through: { attributes: [], }, },}); 出力: { "id": 4, "username": "p4dm3", "points": 1000, "profiles": [ { "id": 6, "name": "queen" } ]} ファインダーメソッド(User.findAll() など)の代わりにミックスイン(user.getProfiles() など)を使用している場合は、代わりに joinTableAttributes オプションを使用する必要があります。 someUser.getProfiles({ joinTableAttributes: ['selfGranted'] }); 出力: [ { "id": 6, "name": "queen", "grant": { "selfGranted": false } }] 多対多対多リレーションシップ、およびそれ以上 ゲームのチャンピオンシップをモデル化しようとしているとします。プレイヤーとチームがあります。チームはゲームをプレイします。ただし、プレイヤーはチャンピオンシップの途中でチームを変更できます(ただし、ゲームの途中ではできません)。したがって、特定の 1 つのゲームを考えると、そのゲームに参加する特定のチームがあり、これらの各チームには(そのゲームのための)プレイヤーのセットがあります。 そこで、まず関連する 3 つのモデルを定義することから始めます。 const Player = sequelize.define('Player', { username: DataTypes.STRING });const Team = sequelize.define('Team', { name: DataTypes.STRING });const Game = sequelize.define('Game', { name: DataTypes.STRING }); さて、問題は、これらをどのように関連付けるかです。まず、次のことに注目します。 1 つのゲームには多くのチームが関連付けられています(そのゲームをプレイしているチーム)。 1 つのチームは多くのゲームに参加している可能性があります。 上記の観察から、Game と Team の間に多対多リレーションシップが必要であることがわかります。このガイドで前述したスーパー多対多リレーションシップを使用しましょう。 // Game と Team の間のスーパー多対多リレーションシップconst GameTeam = sequelize.define('GameTeam', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, },});Team.belongsToMany(Game, { through: GameTeam });Game.belongsToMany(Team, { through: GameTeam });GameTeam.belongsTo(Game);GameTeam.belongsTo(Team);Game.hasMany(GameTeam);Team.hasMany(GameTeam); プレイヤーに関する部分はより厄介です。チームを形成するプレイヤーのセットは、チーム(明らかに)だけでなく、どのゲームが考慮されているかにも依存することに注目します。したがって、Player と Team の間の多対多リレーションシップは望ましくありません。また、Player と Game の間の多対多リレーションシップも望ましくありません。Player をこれらのモデルのいずれかに関連付ける代わりに、必要なのは、Player と「チームとゲームのペアの制約」のようなものとの間の関連です。なぜなら、どのプレイヤーがそこに属するかを定義するのは(チームとゲームの)ペアだからです。したがって、私たちが探しているものは、まさにジャンクションモデルである GameTeam 自体であることがわかります! そして、与えられたゲームとチームのペアが多くのプレイヤーを指定し、一方で同じプレイヤーが多くのゲームとチームのペアに参加できるため、Player と GameTeam の間に多対多リレーションシップが必要であることに注目します! 最大限の柔軟性を提供するために、ここでもスーパー多対多リレーションシップの構築を使用しましょう。 // Player と GameTeam の間のスーパー多対多リレーションシップconst PlayerGameTeam = sequelize.define('PlayerGameTeam', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, },});Player.belongsToMany(GameTeam, { through: PlayerGameTeam });GameTeam.belongsToMany(Player, { through: PlayerGameTeam });PlayerGameTeam.belongsTo(Player);PlayerGameTeam.belongsTo(GameTeam);Player.hasMany(PlayerGameTeam);GameTeam.hasMany(PlayerGameTeam); 上記の関連は、まさに私たちが望むものを達成します。これを完全に実行可能な例で示します。 const { Sequelize, Op, Model, DataTypes } = require('sequelize');const sequelize = new Sequelize('sqlite::memory:', { define: { timestamps: false }, // この例での煩雑さを減らすためだけ});const Player = sequelize.define('Player', { username: DataTypes.STRING });const Team = sequelize.define('Team', { name: DataTypes.STRING });const Game = sequelize.define('Game', { name: DataTypes.STRING });// Game と Team の間にスーパー多対多リレーションシップを適用しますconst GameTeam = sequelize.define('GameTeam', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, },});Team.belongsToMany(Game, { through: GameTeam });Game.belongsToMany(Team, { through: GameTeam });GameTeam.belongsTo(Game);GameTeam.belongsTo(Team);Game.hasMany(GameTeam);Team.hasMany(GameTeam);// Player と GameTeam の間にスーパー多対多リレーションシップを適用しますconst PlayerGameTeam = sequelize.define('PlayerGameTeam', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, },});Player.belongsToMany(GameTeam, { through: PlayerGameTeam });GameTeam.belongsToMany(Player, { through: PlayerGameTeam });PlayerGameTeam.belongsTo(Player);PlayerGameTeam.belongsTo(GameTeam);Player.hasMany(PlayerGameTeam);GameTeam.hasMany(PlayerGameTeam);(async () => { await sequelize.sync(); await Player.bulkCreate([ { username: 's0me0ne' }, { username: 'empty' }, { username: 'greenhead' }, { username: 'not_spock' }, { username: 'bowl_of_petunias' }, ]); await Game.bulkCreate([ { name: 'The Big Clash' }, { name: 'Winter Showdown' }, { name: 'Summer Beatdown' }, ]); await Team.bulkCreate([ { name: 'The Martians' }, { name: 'The Earthlings' }, { name: 'The Plutonians' }, ]); // どのチームがどのゲームに参加したかを定義し始めましょう。これは // いくつかの方法で行えます。たとえば、各ゲームで .setTeams を呼び出すなどです。ただし、 // 簡潔にするために、代わりに直接 create 呼び出しを使用し、直接 // 必要な ID を参照します。ID は 1 から順に与えられることがわかっています。 await GameTeam.bulkCreate([ { GameId: 1, TeamId: 1 }, // この GameTeam は id 1 を取得します { GameId: 1, TeamId: 2 }, // この GameTeam は id 2 を取得します { GameId: 2, TeamId: 1 }, // この GameTeam は id 3 を取得します { GameId: 2, TeamId: 3 }, // この GameTeam は id 4 を取得します { GameId: 3, TeamId: 2 }, // この GameTeam は id 5 を取得します { GameId: 3, TeamId: 3 }, // この GameTeam は id 6 を取得します ]); // 次にプレイヤーを指定しましょう。 // 簡潔にするために、2 番目のゲーム(Winter Showdown)のみで行いましょう。 // s0me0ne と greenhead は The Martians でプレイし、 // not_spock と bowl_of_petunias は The Plutonians でプレイしたとしましょう: await PlayerGameTeam.bulkCreate([ // 'Winter Showdown'(つまり GameTeamIds 3 と 4)の場合: { PlayerId: 1, GameTeamId: 3 }, // s0me0ne は The Martians でプレイしました { PlayerId: 3, GameTeamId: 3 }, // greenhead は The Martians でプレイしました { PlayerId: 4, GameTeamId: 4 }, // not_spock は The Plutonians でプレイしました { PlayerId: 5, GameTeamId: 4 }, // bowl_of_petunias は The Plutonians でプレイしました ]); // これでクエリを実行できます! const game = await Game.findOne({ where: { name: 'Winter Showdown', }, include: { model: GameTeam, include: [ { model: Player, through: { attributes: [] }, // 結果から不要な PlayerGameTeam ネストオブジェクトを隠します }, Team, ], }, }); console.log(Found game: "${game.name}"); for (let i = 0; i < game.GameTeams.length; i++) { const team = game.GameTeams[i].Team; const players = game.GameTeams[i].Players; console.log(- Team "${team.name}" played game "${game.name}" with the following players:); console.log(players.map(p => --- ${p.username}).join('\n')); }})(); 出力: Found game: "Winter Showdown"- Team "The Martians" played game "Winter Showdown" with the following players:--- s0me0ne--- greenhead- Team "The Plutonians" played game "Winter Showdown" with the following players:--- not_spock--- bowl_of_petunias 以上が、スーパー多対多リレーションシップのテクニックを活用することで、Sequelize で 3 つのモデル間の多対多対多リレーションシップを実現する方法です! このアイデアは、さらに複雑な多対多対...対多リレーションシップにも再帰的に適用できます(ただし、ある時点でクエリが遅くなる可能性があります)。
User
パターン 7: 例:
await User.sync({ force: true });console.log('The table for the User model was just (re)created!');
パターン 8: sequelize.sync() を使用すると、すべてのモデルを自動的に同期できます。例:
sequelize.sync()
コード例のパターン
例 1 (css):
/* ... */ { username: { type: DataTypes.TEXT, allowNull: false, unique: true },} /* ... */
例 2 (javascript):
const jane = User.build({ name: 'Jane' });
console.log(jane instanceof User); // trueconsole.log(jane.name); // "Jane"
例 3 (sql):
const [results, metadata] = await sequelize.query('UPDATE users SET y = 42 WHERE x = 12');// Results will be an empty array and metadata will contain the number of affected rows.
例 4 (sql):
SELECT `id`, `status`, `createdAt`, `updatedAt`, `fooId`FROM `bars` AS `bar`WHERE `bar`.`status` = 'open' AND `bar`.`fooId` = 1;
例 5 (javascript):
const project = await Project.findByPk(123);if (project === null) { console.log('Not found!');} else { console.log(project instanceof Project); // true // Its primary key is 123}
参照ファイル
このスキルには、references/ 内に包括的なドキュメントが含まれています:
- advanced.md - 高度なドキュメント
- associations.md - アソシエーションのドキュメント
- data_types.md - データ型のドキュメント
- database.md - データベースのドキュメント
- deployment.md - デプロイメントのドキュメント
- getting_started.md - はじめにのドキュメント
- migrations.md - マイグレーションのドキュメント
- models.md - モデルのドキュメント
- other.md - その他のドキュメント
- querying.md - クエリのドキュメント
- typescript.md - TypeScript のドキュメント
詳細な情報が必要な場合は、view を使用して特定の参照ファイルを読み取ってください。
このスキルの使用方法
初心者の方へ
基礎的な概念については、getting_started またはチュートリアルの参照ファイルから始めてください。
特定の機能について
詳細な情報については、適切なカテゴリの参照ファイル(api、guides など)を使用してください。
コード例について
上記のクイックリファレンスセクションには、公式ドキュメントから抽出された一般的なパターンが含まれています。
リソース
references/
公式ソースから抽出された整理済みのドキュメントです。これらのファイルには以下が含まれます:
- 詳細な説明
- 言語注釈付きのコード例
- 元のドキュメントへのリンク
- クイックナビゲーション用の目次
scripts/
一般的な自動化タスク用のヘルパースクリプトをここに追加してください。
assets/
テンプレート、ボイラープレート、またはサンプルプロジェクトをここに追加してください。
注意事項
- このスキルは公式ドキュメントから自動生成されました
- 参照ファイルはソースドキュメントの構造と例を保持しています
- コード例には、より良いシンタックスハイライトのための言語検出が含まれています
- クイックリファレンスパターンは、ドキュメント内の一般的な使用例から抽出されています
更新
更新されたドキュメントでこのスキルを更新するには:
- 同じ設定でスクレイパーを再実行します
- スキルは最新の情報で再構築されます