はじめに
最近 Sequelize を利用しているのですが、
個人的に他の ORM よりも癖があって使いこなすのが難しいと感じています。。
そのため Sequelize を使用してる時に遭遇した問題と対策を
またハマらないために記録しておこうと思い記事にして残すことにしました
今後も記事の内容は問題に遭遇してから解決次第、
随時アップデートしていく感じを想定しています
データベース関連
データベース接続 からマイグレーション 時の設定等に関する問題の対策方法について載せていきます
1. データベースの設定を環境変数から設定したい
npm install --save sequelize-cli
で Sequelize の CLI 入れた後に、
npx sequelize-cli init
するとディレクトリ直下に .sequelizerc
が生成されます。
.sequelizerc
には各種パス設定が記載されているのですが、
その中の config
の項目の config.json
を config.js
に書き換えます
const path = require('path');
module.exports = {
// config の項目を config.js に設定しておく
"config": path.resolve('./src/config', 'config.js'),
"models-path": path.resolve('./src/models'),
"seeders-path": path.resolve('./src/seeders'),
"migrations-path": path.resolve('./src/migrations')
};
その後、データベースの設定値を環境変数から読み込み、
利用するための記述を config.js を作成して書いていきます
// データベース名に DB_NAME の値を使用する
let database = process.env.DB_NAME;
// データベース接続の際のユーザに DB_USER の値を使用する
let username = process.env.DB_USER;
// データベース接続の際のユーザパスワードに DB_PASSWORD の値を使用する
let password = process.env.DB_PASSWORD;
// データベース接続の際のホストに DB_HOST の値を使用する (デフォは localhost)
let host = process.env.DB_HOST || 'localhost';
// データベース接続の際のポートに DB_PORT の値を使用する (デフォは 5432)
let port = process.env.DB_PORT || '5432';
// dialect の 'postgres' は使用するデータベースにより mysql とかにする
// 使用可能なデータベース一覧 (https://github.com/sequelize/sequelize#installation)
module.exports = {
development: {
database: database,
username: username,
password: password,
host: host,
port: port,
dialect: 'postgres',
define: {
underscored: true
},
},
test: {
database: database,
username: username,
password: password,
host: host,
port: port,
dialect: 'postgres',
define: {
underscored: true
},
},
production: {
database: database,
username: username,
password: password,
host: host,
port: port,
dialect: 'postgres',
define: {
underscored: true
},
},
}
あとは環境変数を適切に設定した後、
npx sequelize-cli db:create
の実行に成功してれば OK です
2. モデルの作成日時や更新日時が自動更新されるようにする
Sequelize で CURRENT_TIMESTAMP
を設定する方法はいくつか確認していたのですが、
PostgreSQL では の書き方で上手く出来たので載せておきます
module.exports = {
up: (queryInterface, Sequelize)=> queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
.then(() => queryInterface.createTable('workspaces', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
created_at: {
allowNull: false,
type: Sequelize.DATE,
// Sequelize.literal を使用することで、
// SQL の関数から値を取得することが可能となる
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
// updated_at フィールドの場合はデータの更新が行われる度に、
// CURRENT_TIMESTAMP の値でフィールドを更新する
onUpdate : Sequelize.literal('CURRENT_TIMESTAMP'),
}
})),
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('workspaces');
}
};
3. primaryKey に UUID を使用する
まずは予め PostgreSQL で UUID の機能を有効にします。
pgcrypto
モジュールの gen_random_uuid
関数を使用することで
UUID を生成することが可能になります
$ psql postgres
psql (11.2)
Type "help" for help.
-- pgcrypto モジュールを有効にする
postgres=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
-- 有効にしたら実際に gen_random_uuid 関数が使える状態になっているか確認する
postgres=# select gen_random_uuid();
gen_random_uuid
--------------------------------------
595c29a3-f1fe-4dcc-8469-05d920e75308
(1 row)
postgres=# \q
無事 UUID が出力出来ていることを確認したら、
primaryKey に UUID を指定したいモデルのマイグレーションファイルを編集します
module.exports = {
up: (queryInterface, Sequelize)=> queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
.then(() => queryInterface.createTable('workspaces', {
id: {
allowNull: false,
primaryKey: true,
// type に UUID を設定して、
// defaultValue には gen_random_uuid() から取得した値を設定する
type: Sequelize.UUID,
defaultValue: Sequelize.literal('gen_random_uuid()'),
},
name: {
allowNull: false,
type: Sequelize.STRING
},
created_at: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
onUpdate : Sequelize.literal('CURRENT_TIMESTAMP'),
}
})),
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('workspaces');
}
};
これでモデル作成時の primaryKey に、
gen_random_uuid
関数で出力した UUID が設定されるようになるはずです
モデル関連
hasMany, belongsTo 等々の Association や find, create 等々の Model の操作に関する問題の対策方法について載せていきます
1. belongsToMany なモデルを include した時に through の attiributes を取り除く
例えば のような Association が定義されていた場合、
FloorCameraLink.belongsToMany(models.FloorCameraLink, {
foreignKey: 'floor_camera_link_connection_id',
through: 'floor_camera_link_connections',
as: 'floor_camera_links',
});
find
で include
オプションを指定して belongsToMany
なモデルを取得する際に、
through
の attributes
を空にすることで取り除くことが可能です
await FloorCameraLink.findByPk(id, {
include: [{
model: sequelize.models.FloorCameraLink,
as: 'floor_camera_links',
// through したテーブルの attributes を明示的に空にする
through: { attributes: [] }
}]
})
2. hasMany なモデルを Count したい
例えば のような Association が定義されていた場合、
Floor.hasMany(models.FloorCameraLink, {
foreignKey: 'floor_id',
as: 'floor_camera_links'
});
group
と include
オプションを指定することで
attributes
に Count フィールドを含めることが可能です
await Floor.findAll({
// 1. モデルの ID で GROUP BY する
group: [`${Floor.name}.id`],
// 2. Count したい hasMany なモデルを include する
include: [{
model: sequelize.models.FloorCameraLink,
as: 'floor_camera_links',
attributes: []
}],
// 3. sequelize.fn を利用することで SQL の COUNT 関数を呼び出す
// 数える対象となるカラムを sequelize.col を利用して第二引数に指定する
// COUNT で取得した値のフィールド名を第三引数に指定する
attributes: [[sequelize.fn('COUNT',
sequelize.col('floor_camera_links.id')),
'total_floor_camera_links']]
})
3. scope を使用すると defaultScope が適用されなくなる
例えば のようにして
scope
を使用した際に defaultScope
が効かなくなってしまいます。。
// viewerFilter という scope のみが適用される
const user = await User.scope('viewerFilter').findOne(options)
こちらが意図した挙動であれば問題ありません。
しかし、そうではなく defaultScope
を適用した状態で、
viewerFilter
も適用したい場合は scope
に両方を記述するようにします
// defaultScope を適用後 viewerFilter という scope を適用する
const user = await User.scope('defaultScope', 'viewerFilter').findOne(options)
4. findAll した項目を一括でアップデートしたい
where した項目を一括でアップデートする際に、
都度 for 文で findAll の結果を回していたのですが、
の書き方で一括でアップデート行うことが可能です
// update を行いたいモデルの update 関数を呼び、
// 第一引数にアップデート項目についての記述を行い、
// 第二引数に where で条件文を記述する
await sequelize.models.Floor.update({
default_floor_camera_link_id: null
}, {
where: {
default_floor_camera_link_id: floor_camera_link.id
}
})
おわりに
実はまだハマっているポイントはいくつかあります。。
それらも解決次第、内容追記しておきたいと思います
参考リンク
- Migrations
- Model | Sequelize
- PostgreSQL 9.4でランダムなUUIDを生成する
- Association | Sequelize
- Best Security Practices for Amazon RDS with Sequelize
- Adding CURRENT_TIMESTAMP as a "DEFAULT" and "ON UPDATE" for createdAt, updatedAt on migrations
- Sequelize 'hasMany' associated(model) count in attributes in query execution
- Eager loading retrieves data from "through" table. How to disable?