スコープ
スコープはコードの再利用を支援するために使用されます。 where
、include
、limit
などのオプションを指定して、一般的に使用されるクエリを定義できます。
このガイドはモデルスコープに関するものです。 アソシエーションスコープに関するガイドも参照してください。類似していますが、同じものではありません。
定義
スコープはモデル定義で定義され、ファインダーオブジェクトまたはファインダーオブジェクトを返す関数にすることができます(デフォルトスコープを除き、デフォルトスコープはオブジェクトのみです)。
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
スコープ定義にスコープ付きモデルを含めることも可能です。これにより、include
、attributes
、where
定義の重複を回避できます。上記の例を使用し、(その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と同様)。ただし、where
とinclude
はマージされます。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
limit
とage
はscope2
によって上書きされ、firstName
は保持されていることに注意してください。limit
、offset
、order
、paranoid
、lock
、raw
フィールドは上書きされますが、where
はデフォルトで浅いマージが行われます(つまり、同一のキーは上書きされます)。フラグwhereMergeStrategy
がand
に設定されている場合(モデルまたは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に追加された非常に強力なマージであり、例で理解するのが最適です。
Foo
、Bar
、Baz
、Qux
というモデルを考え、次のような一対多のアソシエーションがあるとします。
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
などに渡されるオプションでもまったく同じ方法で機能します。