メインコンテンツへスキップ
バージョン: v6 - 安定版

スコープ

スコープはコードの再利用を支援するために使用されます。 whereincludelimitなどのオプションを指定して、一般的に使用されるクエリを定義できます。

このガイドはモデルスコープに関するものです。 アソシエーションスコープに関するガイドも参照してください。類似していますが、同じものではありません。

定義

スコープはモデル定義で定義され、ファインダーオブジェクトまたはファインダーオブジェクトを返す関数にすることができます(デフォルトスコープを除き、デフォルトスコープはオブジェクトのみです)。

class Project extends Model {}
Project.init(
{
// Attributes
},
{
defaultScope: {
where: {
active: true,
},
},
scopes: {
deleted: {
where: {
deleted: true,
},
},
activeUsers: {
include: [{ model: User, where: { active: true } }],
},
random() {
return {
where: {
someNumber: Math.random(),
},
};
},
accessLevel(value) {
return {
where: {
accessLevel: {
[Op.gte]: value,
},
},
};
},
sequelize,
modelName: 'project',
},
},
);

YourModel.addScopeを呼び出すことで、モデル定義後にもスコープを追加できます。これは、include内のモデルが他のモデルの定義時に定義されていない可能性のある、includeを含むスコープに特に役立ちます。

デフォルトスコープは常に適用されます。つまり、上記のモデル定義では、Project.findAll()は次のクエリを作成します。

SELECT * FROM projects WHERE active = true

.unscoped().scope(null)、または別のスコープを呼び出すことで、デフォルトスコープを削除できます。

await Project.scope('deleted').findAll(); // Removes the default scope
SELECT * FROM projects WHERE deleted = true

スコープ定義にスコープ付きモデルを含めることも可能です。これにより、includeattributeswhere定義の重複を回避できます。上記の例を使用し、(そのincludeオブジェクトで条件を直接指定するのではなく)includeされたUserモデルでactiveスコープを呼び出すと、

// The `activeUsers` scope defined in the example above could also have been defined this way:
Project.addScope('activeUsers', {
include: [{ model: User.scope('active') }],
});

使用方法

スコープは、モデル定義で.scopeを呼び出し、1つ以上のスコープの名前を渡すことによって適用されます。.scopeは、.findAll.update.count.destroyなどの通常のメソッドをすべて備えた、完全に機能するモデルインスタンスを返します。このモデルインスタンスを保存して、後で再利用できます。

const DeletedProjects = Project.scope('deleted');
await DeletedProjects.findAll();

// The above is equivalent to:
await Project.findAll({
where: {
deleted: true,
},
});

スコープは、.find.findAll.count.update.increment.destroyに適用されます。

関数のスコープは2つの方法で呼び出すことができます。スコープが引数を取らない場合は、通常どおり呼び出すことができます。スコープが引数を取る場合は、オブジェクトを渡します。

await Project.scope('random', { method: ['accessLevel', 19] }).findAll();

生成されたSQL

SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19

マージ

複数のスコープを同時に適用するには、.scopeにスコープの配列を渡すか、スコープを連続した引数として渡します。

// These two are equivalent
await Project.scope('deleted', 'activeUsers').findAll();
await Project.scope(['deleted', 'activeUsers']).findAll();

生成されたSQL

SELECT * FROM projects
INNER JOIN users ON projects.userId = users.id
WHERE projects.deleted = true
AND users.active = true

デフォルトスコープと併せて別のスコープを適用する場合は、defaultScopeキーを.scopeに渡します。

await Project.scope('defaultScope', 'deleted').findAll();

生成されたSQL

SELECT * FROM projects WHERE active = true AND deleted = true

複数のスコープを呼び出す場合、後続のスコープのキーは前のスコープのキーを上書きします(Object.assignと同様)。ただし、whereincludeはマージされます。2つのスコープを考えます。

YourModel.addScope('scope1', {
where: {
firstName: 'bob',
age: {
[Op.gt]: 20,
},
},
limit: 2,
});
YourModel.addScope('scope2', {
where: {
age: {
[Op.lt]: 30,
},
},
limit: 10,
});

.scope('scope1', 'scope2')を使用すると、次のWHERE句が生成されます。

WHERE firstName = 'bob' AND age < 30 LIMIT 10

limitagescope2によって上書きされ、firstNameは保持されていることに注意してください。limitoffsetorderparanoidlockrawフィールドは上書きされますが、whereはデフォルトで浅いマージが行われます(つまり、同一のキーは上書きされます)。フラグwhereMergeStrategyandに設定されている場合(モデルまたはSequelizeインスタンスで)、whereフィールドはand演算子を使用してマージされます。

たとえば、YourModelが次のように初期化されている場合

YourModel.init(
{
/* attributes */
},
{
// ... other init options
whereMergeStrategy: 'and',
},
);

.scope('scope1', 'scope2')を使用すると、次のWHERE句が生成されます。

WHERE firstName = 'bob' AND age > 20 AND age < 30 LIMIT 10

複数の適用されたスコープのattributesキーは、attributes.excludeが常に保持されるようにマージされます。これにより、複数のスコープをマージし、最終的なスコープで機密フィールドが漏洩しないようにすることができます。

スコープ付きモデルでfindAll(および同様のファインダー)にファインダーオブジェクトを直接渡した場合にも、同じマージロジックが適用されます。

Project.scope('deleted').findAll({
where: {
firstName: 'john',
},
});

生成されたwhere句

WHERE deleted = true AND firstName = 'john'

ここでは、deletedスコープがファインダーとマージされています。ファインダーにwhere: { firstName: 'john', deleted: false }を渡すと、deletedスコープは上書きされます。

includeのマージ

includeは、含まれるモデルに基づいて再帰的にマージされます。これはv5に追加された非常に強力なマージであり、例で理解するのが最適です。

FooBarBazQuxというモデルを考え、次のような一対多のアソシエーションがあるとします。

const Foo = sequelize.define('Foo', { name: Sequelize.STRING });
const Bar = sequelize.define('Bar', { name: Sequelize.STRING });
const Baz = sequelize.define('Baz', { name: Sequelize.STRING });
const Qux = sequelize.define('Qux', { name: Sequelize.STRING });
Foo.hasMany(Bar, { foreignKey: 'fooId' });
Bar.hasMany(Baz, { foreignKey: 'barId' });
Baz.hasMany(Qux, { foreignKey: 'bazId' });

次に、Fooに定義された次の4つのスコープを考えます。

Foo.addScope('includeEverything', {
include: {
model: Bar,
include: [
{
model: Baz,
include: Qux,
},
],
},
});

Foo.addScope('limitedBars', {
include: [
{
model: Bar,
limit: 2,
},
],
});

Foo.addScope('limitedBazs', {
include: [
{
model: Bar,
include: [
{
model: Baz,
limit: 2,
},
],
},
],
});

Foo.addScope('excludeBazName', {
include: [
{
model: Bar,
include: [
{
model: Baz,
attributes: {
exclude: ['name'],
},
},
],
},
],
});

これらの4つのスコープは、たとえばFoo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()を呼び出すことで簡単に深くマージできます。これは、次を呼び出すことと完全に同等です。

await Foo.findAll({
include: {
model: Bar,
limit: 2,
include: [
{
model: Baz,
limit: 2,
attributes: {
exclude: ['name'],
},
include: Qux,
},
],
},
});

// The above is equivalent to:
await Foo.scope(['includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName']).findAll();

4つのスコープがどのように1つにマージされたかに注目してください。スコープのincludeは、含まれるモデルに基づいてマージされます。あるスコープがモデルAを含み、別のスコープがモデルBを含む場合、マージされた結果はモデルAとモデルBの両方を含みます。一方、両方のスコープが同じモデルAを含んでいるが、異なるオプション(ネストされたincludeやその他の属性など)を持つ場合、上記のようにそれらは再帰的にマージされます。

上記で説明したマージは、スコープに適用される順序に関係なく、まったく同じ方法で機能します。順序は、特定のオプションが2つの異なるスコープによって設定されている場合(上記の例では、各スコープが異なることを行っているため、そうではありません)にのみ違いを生じます。

このマージ戦略は、.findAll.findOneなどに渡されるオプションでもまったく同じ方法で機能します。