九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、前回の記事「Node.jsにDBマイグレーション、Seed、モデルを用意する」では、「Sequelize」というパッケージを使ってデータベース周りの開発を楽にしてみました。
前回は、その「Sequelize」のインストールやコマンドの使い方をメインにしてご紹介したのですが、実はひとつ情報が多すぎてまとめていない部分があります。
それは・・・
モデルを使ってデータベース操作(取得/追加/更新/削除)する方法
です。
そこで!
今回は「Sequelize」モデルの使い方を実例でお届けしたいと思います。
ぜひ皆さんのお役に立てると嬉しいです😊✨
開発環境: Node.js 8.10、Express 4.1、Sequelize 5.2
目次
前提として
今回の記事は「sequelize」がインストールされていて、さらに、すでにモデルが作成されている必要があります。
もしまだ準備が整っていない場合は、前回記事の「モデルを使う」を参考にしてください。
データを取得する
findByPk(): IDを使ってデータ取得
例えば、ID番号が1
のユーザーデータを取得したい場合は、findByPk()
を使います。
User.findByPk(1).then(user => { // });
findOne(): 条件をみたす最初のデータを取得する
例えば、IDが3
, 4
, 5
のうちで一番最初のデータ(つまり、ID: 3
)のデータを取得する場合です。
User.findOne({ where: { id: [3, 4, 5] } }).then(user => { // });
findAll(): 条件をみたす全てのデータを取得する
例えば、IDが3
, 4
, 5
に該当するデータを全て取得する場合です。
User.findAll({ where: { id: [3, 4, 5] } }).then(users => { // });
findOrCreate(): 条件をみたすデータがなければ新規作成
例えば、IDが6
のものを探し、もしなければデータを新規登録する場合です。
User.findOrCreate({ where: { id: 6 }, defaults: { // 新規登録するデータ name: '六郎', email: 'rokuro@example.com', password: 'rokuro-password', createdAt: new Date(), updatedAt: new Date() } }).then(([user, created]) => { if(created) { // データが新規作成された場合 // } });
※ ちなみに、then()
の中身が[user, created]
と配列になっていることに注意してください。
findAndCountAll(): ページ処理のデータを取得する
例えば、1ページごとに10件ずつ表示していて、2ページ目のデータを取得したい場合です。
const page = 2; const perPage = 10; User.findAndCountAll({ offset: (page - 1) * perPage, limit: perPage }).then(result => { const count = result.count; // 全データの件数 const rows = result.rows; // 条件をみたすデータ });
※ちなみにhasOne
やhasMany
などの結合を使う場合は、distinct
をtrue
にしてください。そうしないと間違った件数が返ってくる可能性があります。
const page = 2; const perPage = 10; User.findAndCountAll({ offset: (page - 1) * perPage, limit: perPage, include: [{ model: Post }], distinct:true }).then(result => { // 省略 });
※MySQL
などの環境によって、distinct
をtrue
にするとエラーになってしまう場合があります。これは、DISTINCT(*)
という表現が使えないためで、その場合は、include
に空白の配列をセットしてみてください。
findAndCountAll({ distinct: true, include: [] })
また、findAndCountAll()
は少し不便にもページ処理で重要な「最後のページ番号」や「現在が最後のページかどうか」などの情報を提供してくれません。そのため、以下のようにして計算するといいでしょう。
// 省略 .then(data => { data.lastPage = (data.count > 0) ? Math.ceil(data.count / perPage) : 1; data.isLast = (page === data.lastPage); res.send(data); });
count(): 該当するデータの件数を取得する
条件に合うデータの「件数」を取得する場合は、count()
を使います。
User.count({ where: { id: [2, 3, 4] } }).then(dataCount => { // });
max(): 該当する最大値を取得する
条件に合うデータの最大値を取得する場合はmax()
を使います。
User.max('id', { where: { id: [2, 3, 4] } }).then(maxId => { // });
min(): 該当する最小値を取得する
条件に合うデータの最小値を取得する場合はmin()
を使います。
User.min('id', { where: { id: [2, 3, 4] } }).then(minId => { // });
sum(): 該当するデータの合計を取得する
例えば、テストで取得した点数の合計を取得する場合です。
User.sum('score', { where: { id: [2, 3, 4] } }).then(scoreTotal => { // });
データ取得の設定をする
where: 条件を指定する
sequlize
の条件指定は、専用の比較演算子を使えるようにしておく必要があります。まずは以下のコードを追加しておいてください。
const Sequelize = require('sequelize'); const Op = Sequelize.Op;
では、ひとつずつ見ていきましょう!
※ なお、SQL文も例として載せていますが、それらは分かりやすく説明するためにテーブル名などを省いた簡略バージョンとなっています。
基本的な使い方(特定の値をもつデータを取得)
例えばID:3
のデータを取得する場合はこのようになります。
User.findOne({ where: { id: 1 } }).then(user => { // });
もちろん数値だけでなく、文字列も使えます。
WHERE
句はこうなります。
WHERE id = 3
また、同じようにしてNULL
も問題なく使えますが、その場合WHERE
句はIS
を使う形になる場合があります。
User.findAll({ where: { deletedAt: null } }).then(users => {})
WHERE deletedAt IS NULL
AND条件
例えば、メールアドレスが「taro@example.com」で、しかも名前が「太郎」のデータを取得する場合です。
User.findOne({ where: { [Op.and]: { email: 'taro@example.com', name: '太郎' } } }).then(user => { // });
WHERE
句はこのようになります。
WHERE email = 'taro@example.com' AND name = '太郎'
OR条件
例えば、メールアドレスが「taro@example.com」か、もしくは名前が「次郎」のデータを取得する場合です。
User.findAll({ where: { [Op.or]: { email: 'taro@example.com', name: '次郎' } } }).then(users => { // });
WHERE
句はこのようになります。
WHERE email = 'taro@example.com' OR name = '太郎'
特定の数値「超」のデータを取得する
例えば、「id > 3」の条件に合うデータを取得する場合です。(つまり、id:3
は含まない)
User.findAll({ where: { id: { [Op.gt]: 3 } } }).then(users => { // });
※「gt」は、greater than
と言う意味です。
特定の数値「以上」のデータを取得する
例えば、「id >= 3」の条件に合うデータを取得する場合です。(つまり、id:3
を含む)
User.findAll({ where: { id: { [Op.gte]: 3 } } }).then(users => { // });
※「gte」はgreater than / equal
という意味です。
特定の数値「未満」のデータを取得する
例えば、「id < 3」の条件に合うデータを取得する場合です。(つまり、id:3
は含まない)
User.findAll({ where: { id: { [Op.lt]: 3 } } }).then(users => { // });
※「lt」は、less than
と言う意味です。
特定の数値「以下」のデータを取得する
例えば、「id <= 3」の条件に合うデータを取得する場合です。(つまり、id:3
を含む)
User.findAll({ where: { id: { [Op.lte]: 3 } } }).then(users => { // });
※「lte」は、less than / equal
という意味です。
特定の値「以外」のデータを取得する
例えば、IDが 3 以外のデータを取得する場合です。
User.findAll({ where: { id: { [Op.ne]: 3 } } }).then(users => { // });
※「ne」はnot equal
という意味です。
WHERE
句はこうなります。
WHERE id != 3
特定の範囲内のデータを取得する
例えば、IDが3
〜5
のデータを取得する場合です。(ID:3
と ID:5
を含むデータが取得されます)
User.findAll({ where: { id: { [Op.between]: [3, 5] } } }).then(users => { // });
特定の範囲「以外」のデータを取得する
例えば、IDが3
〜5
「以外」のデータを取得する場合です。(ID:3 〜 5
は、含まない)
User.findAll({ where: { id: { [Op.notBetween]: [3, 5] } } }).then(users => { // });
指定した複数の値をもつデータを取得する
例えば、IDが3
と5
のものだけを取得する場合です。
User.findAll({ where: { id: { [Op.in]: [3, 5] } } }).then(users => { // });
WHERE
句はこうなります。
WHERE id IN (3, 5)
指定した複数の値を「もたない」データを取得する
例えば、IDが3
と5
以外のデータだけを取得する場合です。
User.findAll({ where: { id: { [Op.notIn]: [3, 5] } } }).then(users => { // });
WHERE
句はこうなります。
WHERE id NOT IN (3, 5)
あいまい検索する
例えば、メールアドレスに「@example」が含まれているデータを取得する場合です。
User.findAll({ where: { email: { [Op.like]: '%@example%' } } }).then(users => { // });
また、メールアドレスが「taro@」から始まるデータを取得する場合はこちら。
User.findAll({ where: { email: { [Op.like]: 'taro@%' } } }).then(users => { // });
そして、メールアドレスが「@example.com」で終わるデータを取得する場合です。
User.findAll({ where: { email: { [Op.like]: '%@example.com' } } }).then(users => { // });
WHERE
句は、それぞれこうなります。
WHERE email LIKE '%@example%' WHERE email LIKE 'taro@%' WHERE email LIKE '%@example.com'
あいまい検索「以外のデータ」を取得する
例えば、メールアドレスに「@example」が含まれていないデータを取得する場合です。
User.findAll({ where: { email: { [Op.notLike]: '%@example%' } } }).then(users => { // });
WHERE
句はこうなります。
WHERE email NOT LIKE '%@example%'
指定の値以外のデータを取得する
例えば、年齢がnull
以外のデータを取得する場合です。
User.findAll({ where: { age: { [Op.not]: null } } }).then(users => { // });
WHERE
句はこうなります。
WHERE age IS NOT NULL
order: 並び順を指定する
小 → 大の順に並べる
例えば、年齢の小さい方から並べ替える場合です。
User.findAll({ order: [ ['age', 'ASC'] ] }).then(users => { // });
大 → 小の順に並べる
例えば、年齢の大きい方から並べ替える場合です。
User.findAll({ order: [ ['age', 'DESC'] ] }).then(users => { // });
ランダムな順に並べる
ランダムな順番にデータを並べ替える場合は、次のようにします。
User.findAll({ order: sequelize.random() }).then(users => { // });
※ sequelize
インスタンスについては、Sequelizeインスタンスを取得するをご覧ください。
結合した先で並べ替え
例えば、「本」と「本のカテゴリ」が「hasOne(1:1)」で結合していて、「カテゴリ名」で並べ替えを行う場合です。
const Book = require('./models').Book; const BookCategory = require('./models').BookCategory; Book.findAll({ include: [ { model: BookCategory } ], order: [ [BookCategory, 'name', 'ASC'] ] }).then(books => { // });
指定した値の順で並べ替え
例えば、自分で指定したID番号の順に並べ替えをしたい場合です。今回は、id
が 5
, 1
, 3
の順で並べ替えるようにしてみます。
User.findAll({ order: [[sequelize.fn('FIELD', sequelize.col('id'), 5, 1, 3), 'ASC']] }).then(users => { // });
なお、ASC
をDESC
に変更すると逆順に変更できます。
さらに、以下のように「スプレッド構文」を使うと、変数にid
情報が入っている場合でも楽にコーディングできます。
const ids = [5, 1, 3]; User.findAll({ order: [[sequelize.fn('FIELD', sequelize.col('id'), ...ids), 'ASC']] }).then(users => { // });
※ sequelize
インスタンスについては、Sequelizeインスタンスを取得するをご覧ください。
※また、利用するSQLによっては使えない場合があります。(MySQLはOKでした)
自分で指定する
「Sequelize」に頼らず、自分自信でORDER
句を指定する場合は、このようになります。
User.findAll({ order: sequelize.literal('age ASC') }).then(users => { // });
※ sequelize
インスタンスについては、Sequelizeインスタンスを取得するをご覧ください。
group: グループ化する
例えば、「本」を「カテゴリID」でグループ化し、カテゴリごとの件数を取得する場合です。
Book.findAll({ attributes: [ 'category_id', [sequelize.fn('COUNT', sequelize.col('category_id')), 'category_id_count'] ], group: 'category_id' }).then(books => { // });
※複数フィールドでグループ化する場合は、配列で指定してください。
※ sequelize
インスタンスについては、Sequelizeインスタンスを取得するをご覧ください。
attributes: 取得するフィールドを指定する
基本的な指定方法
例えば、Users
テーブルから「name」と「email」だけを取得する場合です。
User.findAll({ attributes: ['name', 'email'] }).then(users => { // });
特定のフィールドを除外する
例えば、「createdAt」と「updatedAt」だけ除外してデータ取得する場合です。
User.findAll({ attributes: { exclude: ['createdAt', 'updatedAt'] } }).then(users => { // });
COUNT()でカウントする
いわゆるCOUNT()
を使いたい場合です。
詳しくは、グループ化するをご覧ください。
limit: 取得する件数を指定する
取得するデータ件数を指定する場合です。
詳しくは、ページ処理のデータを取得するをご覧ください。
offset: 取得する開始位置を指定する
取得するデータの開始位置を指定する場合です。
詳しくは、ページ処理のデータを取得するをご覧ください。
Eager Loadingを設定する
Eager Loading
とは、簡単に言うと「2つ以上のテーブルを結合して一気にデータを取得する」ことです。
ひとつずつ見ていきましょう!
hasOne
例えば、「本」のデータに「本のカテゴリ」が1つだけ設定されている、つまり「本」と「本のカテゴリ」が「1:1」の関係で結合する場合です。
まず、「models/book.js」を開いてBook
モデルにhasOne
の結合情報を追加します。
'use strict'; module.exports = (sequelize, DataTypes) => { const Book = sequelize.define('Book', { name: DataTypes.STRING, category_id: DataTypes.INTEGER }, {}); Book.associate = function(models) { Book.hasOne(models.BookCategory, { sourceKey: 'category_id', foreignKey: 'id' }); }; return Book; };
そして、include
を使ってデータ取得します。
Book.findAll({ include: [ { model: BookCategory } ] }).then(books => { for(let book of books) { console.log(book.BookCategory); } });
hasMany
例えば、「ユーザー」と「ユーザーが投稿したコンテンツ(複数の可能性あり)」を結合した「1:多」の関係になるよう結合する場合です。
まず、「models/user.js」を開いてUser
モデルにhasMany
の結合情報を追加します。
'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { name: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING }, {}); User.associate = function(models) { User.hasMany(models.Post, { foreignKey: 'user_id', sourceKey: 'id' }); }; return User; };
そして、include
を使ってデータ取得します。
User.findAll({ include: [ { model: Post } ] }).then(users => { for(let user of users) { console.log(user.Posts); } });
BelongsTo
例えば、「ユーザー詳細」と「ユーザー」のテーブルが「1:1」の関係で結合する場合です。
まず、「models/userdetail.js」を開いてUserDetail
モデルにbelongsTo
の結合情報を追加します。
'use strict'; module.exports = (sequelize, DataTypes) => { const UserDetail = sequelize.define('UserDetail', { zip: DataTypes.STRING, address: DataTypes.STRING }, {}); UserDetail.associate = function(models) { UserDetail.belongsTo(models.User, { foreignKey: 'id', sourceKey: 'user_id' }); }; return UserDetail; };
そして、include
を使ってデータ取得します。
UserDetail.findAll({ include: [ { model: User } ] }).then(userDetails => { for(let detail of userDetails) { console.log(detail.User); } });
BelongsToMany
例えば、まず以下2つのテーブルがあるとします。
- Users ・・・ ユーザー
- Posts ・・・ ユーザーが投稿した記事
そして、ここにもう1つ「ユーザーが【いいね!】をしたデータ」を管理する「PostLikes」テーブルをつくります。
この場合、ユーザーから見ると複数の記事に「いいね!」したデータが存在していて、さらに、記事から見ても複数のユーザーが「いいね!」してるので、「多:多」の関係になっています。つまり、PostLikes
は中間テーブルになっています。
では、記事データを基準として、「いいね!」したユーザーのデータも一緒に取得してみましょう。
まず、「models/post.js」を開いてPost
モデルにbelongsToMany
の結合情報を追加します。
'use strict'; module.exports = (sequelize, DataTypes) => { const Post = sequelize.define('Post', { user_id: DataTypes.INTEGER, content: DataTypes.TEXT }, {}); Post.associate = function(models) { Post.belongsToMany(models.User, { through: models.PostLike, foreignKey: 'post_id', otherKey: 'user_id', as: 'likes' }); }; return Post; };
※ ちなみに、分かりやすくするために「likes」という名前をつけています。
そして、include
を使ってデータ取得します。
Post.findAll({ include: [ { model: User, as: 'likes' } ] }).then(posts => { // });
SetterとGetterでデータを加工する
Getterの使い方
例えば、以下のようにユーザーテーブルに「firstName(名)」「lastName(姓)」が存在しているとします。
そして、データ取得したときにこの2つの値を結合した値「fullName」を取得する場合です。
「/models/user.js」を開いて以下のように設定してください。
'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { firstName: DataTypes.STRING, lastName: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING }, { getterMethods: { fullName() { return this.lastName +' '+ this.firstName; } } }); User.associate = function(models) { // }; return User; };
これで、fullName
を通して結合された名前を取得することができます。
User.findOne({ where: { id: 1 } }).then((user) => { console.log(user.fullName); // 例: 山田 太郎 });
なお、もし初期値にFullName
を追加しておきたい場合は以下のようにDataTypes.VIRTUAL
を使ってください。
'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { firstName: DataTypes.STRING, lastName: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING, fullName: DataTypes.VIRTUAL, // ←ここ }, { getterMethods: { fullName() { return this.lastName +' '+ this.firstName; } } }); User.associate = function(models) { // }; return User; };
Setterの使い方
例えば、fullName
に「新山田 新太郎」とセットされたら、それぞれ「firstName」と「lastName」にデータセットする場合です。
「/models/user.js」を開いて以下のように設定してください。
'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { firstName: DataTypes.STRING, lastName: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING }, { setterMethods: { fullName(value) { const [lastName, firstName] = value.split(' '); console.log(lastName, firstName) this.setDataValue('firstName', firstName); this.setDataValue('lastName', lastName); } } }); User.associate = function(models) { // }; return User; };
これで、以下のようにすれば「firstName」「lastName」が一気にセットできるようになります。
User.findOne({ where: { id: 1 } }).then((user) => { user.fullName = '新山田 新太郎'; user.save(); });
※ ただし、これはサンプルなので実際の開発ではtrim()
やバリデーションなどで正しいデータを用意することを忘れないでください。
Scopeを使う
Scope
とは、「検索や並べ替えの条件」を使いまわしができるようにする機能です。
例えば、あなたのサイトでよく「承認済のユーザー」を抽出することがあるとしましょう。
通常であれば、以下のようにその都度where
で指定することになります。
{ where: { accepted: true } }
ただ、毎回毎回この記述をするのはめんどうですし、どこかで記述ミスが作られてしまうかもしれません。
そこで、Scope
の登場です。Scope
はこの面倒な部分を「共通化」して、いつでも呼び出せるようにすることができます。(もちろん、Scope
にはwhere
だけでなく、order
やattributes
なども使えます)
では実際の例を見てみましょう!
Global scope
グローバル・スコープは、特に設定しなくても必ず追加してくれる条件のことです。
例えば、「秘密の質問」や「秘密の質問の答え」は、基本的に取得すべきではない特別なデータです。ということで、以下のようにグローバルスコープを追加してこれらのデータを取得しないようにしてみましょう。
module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { // 省略 }, { defaultScope: { attributes: { exclude: ['secret_question', 'secret_answer'] }, }, // 省略
Scope
次に使う時には自分で呼び出して使う通常のスコープです。今回はhasMany
の結合をするためのスコープを作ってみましょう。
User.associate = (models) => { User.addScope('posts', { include: [{ model: models.Post }] }); }
※ちなみに本家のドキュメントでは{ scope: { ***** }}
でも設定できると書かれていますが、結合を含む場合はうまくいきません。モデルが全て読み込まれていないためです。
そして、この「posts」スコープを使うには以下のようにしてください。
User.scope('posts').findAll(); // もしくは複数の場合 User.scope(['posts', 'friends']).findAll();
また、スコープにはパラメーターを渡すこともできます。
その場合は、以下のように関数化して設定します。
例としてuserId
をパラメーターとして渡してみましょう。
User.associate = (models) => { User.addScope('posts', (userId) => { return { where: { id: userId } } }); }
使い方はこうなります。
const userId = 1; User.scope({ method: ['posts', userId] }).findAll();
データを追加する
1つだけ追加
データを1つ(1行)だけ追加する場合はcreate()
を使います。
User.create({ name: '六郎', email: 'rokuro@example.com', password: 'rokuro-password', age: 22, accepted: false, createdAt: new Date(), updatedAt: new Date() }).then(user => { // });
複数データを追加
例えば、複数のユーザーを一気に追加する場合はbulkCreate()
を使います。
そのため、Seeder
などで重宝するかもしれません。
const now = new Date(); User.bulkCreate([ { name: '太郎', email: 'taro@example.com', password: 'taro-password', createdAt: now, updatedAt: now}, { name: '次郎', email: 'jiro@example.com', password: 'jiro-password', createdAt: now, updatedAt: now}, { name: '三郎', email: 'saburo@example.com', password: 'saburo-password', createdAt: now, updatedAt: now}, { name: '四郎', email: 'shiro@example.com', password: 'shiro-password', createdAt: now, updatedAt: now}, { name: '五郎', email: 'goro@example.com', password: 'goro-password', createdAt: now, updatedAt: now} ]).then(users => { // });
データを更新する
update()を使う場合
条件に合うデータ全てを更新する場合は、update()
を使います。
例えば、以下はid:1
の名前を「真太郎」、メールアドレスを「real-taro@example.com」に変更する場合です。
User.update( { name: '真太郎', email: 'real-taro@example.com' }, { where: { id: 1 } } ).then(() => { // });
※ この場合UpdatedAt
も自動的に更新されます。
save()を使う場合
すでにfindOne()
などでデータを取得している場合に使える方法です。
User.findOne().then(user => { user.name = '新太郎'; user.email = 'shintaro@example.com'; user.save(); });
※ この場合UpdatedAt
も自動的に更新されます。
updatedAtの日時だけを更新する
updatedAt
は、他のデータが更新された場合に自動的に新しい日時が保存されるようになっているのですが、逆に言うと「他のデータが更新されないなら、ずっと古いまま」という意味になります。
そのため、もし手動でupdatedAt
だけを更新したい場合は以下のようにします。
User.findOne().then(user => { user.updatedAt = new Date(); user.changed('updatedAt', true); user.save(); });
データを削除する
fineOne()などでデータ取得されている場合
すでにデータ取得されている場合は、インスタンス本体からdestroy()
を実行します。
User.findOne({ where: { id: 10 } }).then(user => { user.destroy(); });
条件に合う全データを削除する場合
条件に合うデータ全てを削除する場合は、モデルのdestroy()
を実行します。
User.destroy({ where: { id: [3, 5] } }).then(() => { // });
ソフトデリートを使う場合
ソフトデリートとは、データベースから実際にデータを削除するのではなく、削除されたことがわかるデータを用意しておき「削除されたもの」として扱う機能のことです。(なぜなら、物理的にデータ削除してしまうと永遠に復活できないからです)
ソフトデリートを有効にする
「Sequelize」の場合、「deletedAt」というフィールド(初期値:null
)を用意し、ここに日付データが入っていれば「削除されたもの」として扱われます。
では、例としてUser
モデルにソフトデリート機能をつけてみましょう。
マイグレーションは、以下のようにdeletedAt
を追加して実行してください。(Null
は有効にする)
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Users', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, name: { type: Sequelize.STRING }, email: { type: Sequelize.STRING }, password: { type: Sequelize.STRING }, deletedAt: { type: Sequelize.DATE }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Users'); } };
テーブルはこうなります。
では、次に「/models/user.js」を開いてparanoid
オプションを追加してください。
'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { name: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING }, { paranoid: true }); User.associate = function(models) { // }; return User; };
これでソフトデリートが有効になりました。
実際に削除してテーブルを確認してみましょう。
このように日付が追加されます。
そして、この状態でfindAll()
してもID:3
とID:5
のデータは取得されることはありません。
ソフトデリートされたデータも含めてデータ取得する
もし、ソフトデリートされたデータも含めて取得したい場合はparanoid
オプションを使います。
User.findAll({ paranoid: false }).then(users => { // });
ソフトデリートせず、物理(完全)削除する
ソフトデリートを使っているけれども、物理的に削除したいという場合はforce
オプションを使います。
User.destroy({ where: { id: [3, 5] }, force: true }).then(() => { // });
ソフトデリートされたデータを復活させる
すでにソフトデリートされたデータを復活させるにはrestore()
を使います。
User.findOne({ where: { id: 1 }, paranoid: false }).then(user => { user.restore(); });
直接SQL文を実行する
直接SQL文を実行する場合は、query()
を使います。
const Sequelize = require('sequelize'); const config = require('./config/config'); const sequelize = new Sequelize(config[process.env.NODE_ENV]); sequelize.query('SELECT COUNT(id) FROM Users').then(users => { // });
その他
Sequelizeインスタンスを取得する
環境による自動切り替えや、少し特殊な内容を作成しようとする場合に「Sequelize」のインスタンスが必要になることがあります。
そんな場合は次のようにして取得してください。
const Sequelize = require('sequelize'); const config = require('./config/config'); // Sequelize インスタンス const sequelize = new Sequelize({ dialect: config[process.env.NODE_ENV].dialect });
※ dialect
に「mysql」と直書きしてしまうと環境が変わったときにエラーが発生する可能性があるので少し複雑な形にしていますが、その心配が無い場合は直接書き込んでもいいでしょう。
タイムゾーンを指定する
Sequelize
には、自動的に「createdAt」や「updatedAt」の日時を保存してくれる機能がありますが、実行環境によってはタイムゾーンが正しくない場合があります。そのため、もし日本のタイムゾーンに設定したい場合は、config/config.json
の中で以下のように設定してください。
{ "development": { // 省略 "timezone": "+09:00" }, // 省略
なお、Laravel
などでもよく設定する「Asia/Tokyo」という表現を使うと、次のような警告が表示されることがあります。
Ignoring invalid timezone passed to Connection: Asia/Tokyo. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection
(超意訳)タイムゾーンが正しくないから無視するね。これ、今は警告だけど将来はエラーになるから(気をつけて〜😏)
各SQLの関数を使う
例えば、MySQLにはISNULL()
という関数がありますが、この関数を使って以下のような順で並べ替える場合です。
- データがNULLのもの(が先)
- NULL以外のもの(が後)
order: [ [sequelize.fn('isnull', sequelize.col('checkedAt')), 'DESC'], ['checkedAt', 'ASC'] ],
※ sequelize
インスタンスについては、Sequelizeインスタンスを取得するをご覧ください。
インスタンスメソッドを追加する
例えば、以下のようにDBから取得したデータから独自メソッドを使いたい場合です。
User.findByPk(1).then(user => { const password = 'xxxxx'; if(user.isValidPassword(password)) { console.log('正しいパスワードです!'); } });
この場合は、以下のようにモデルの中に独自のメソッドを追加してください。
'use strict'; const bcrypt = require('bcrypt'); module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { name: DataTypes.STRING, email: DataTypes.STRING, emailVerifiedAt: DataTypes.DATE, password: DataTypes.STRING, rememberToken: DataTypes.STRING }, {}); User.associate = function(models) { // associations can be defined here }; User.prototype.isValidPassword = function(password){ if(!password) return false; return bcrypt.compareSync(password, this.password) }; return User; };
おわりに
ということで、今回は「Sequelize」のモデルをどのように使うかを実例でご紹介しました。
利用できることが多いので少し長い記事になってしまいましたが、ブックマークしてチートシート(カンペ)として使っていただけると嬉しいです。
なお、Laravel
好きな私の雑感としては、
「(どちらが後か先かではなく)似ているので開発しやすい!」
でした。
例えば、Setter
やGetter
はそのままAccessor
とMutator
ですし、createdAt
やupdatedAt
、deletedAt
というフィールド名もほぼ同じ。hasOne
, hasMany
などの設定も似ていますよね。
そんな中、1点Laravel
と違うなと感じたのはWHERE
句やORDER
句の指定方法です。Laravel
はメソッドを使って設定していきますが、Sequelize
ではオブジェクトとして一気にセットする形になっています。(ここは、個人的にLaravel
の方が使いやすいかな、という感じです。元々この形式が嫌でCakePHP
を使うのをやめましたので・・・😅)
例えば、以下のような形がいいな・・・なんて。(Laravel
依存症しょうか😂)
// この例は正しくありません!私の希望(妄想??)です User.findOne(query => { query.where('id', 1); query.orderBy('createdAt', 'ASC'); query.hasWhere(UserDetail, q => { q.where('age', '>', 20); }); }).then(user => {});
ただ、やはり人気のパッケージだけあって、いまのところ「Node.js」でDB周りの開発をするなら「Sequelize」は外せないだろう、という考えです。
ぜひ皆さんもこの便利さを体験してみてくださいね。
ではでは〜♪