Sequelizeの挙動を確認するために、rails generate scaffold
でできるサンプルアプリをSequelizeとexpress.jsで作ってみる。
今回やるのは、rails g scaffold user name:string age:integer
した結果と同じようなものを作る。
コレ↓ (下はrailsのscaffoldで作ったもの)
Sequelizeはこちら。
https://github.com/sequelize/sequelize
ドキュメントはこちら。
http://docs.sequelizejs.com/en/v3/
セットアップ
$ express -e sequelize-express
$ npm install
$ npm install --save sequelize
$ npm install --save mysql
# 関係無いけどlint入れた
$ npm install --save-dev eslint
sequelizeコマンド使ってモデルやmigration作ったりすると便利なので、入れてない場合はsequelize-cliも入れておく。
https://github.com/sequelize/cli
$ npm install -g sequelize-cli
Model, Migration
ここからmodel, migrationをざっと作っていく。
この辺りの流れはこちらの記事がわかりやすかった。
参考: nodejs + express + sequelize webサーバ最小構成
また、あんまりドキュメントが詳しく無いけど、$ sequelize help:[command name]
みたいな感じでhelp見ると詳しく書いてるのでオススメ。
$ sequelize init
これで、下記のように作成される。/config/config.json
にDBの情報が記載されている。
├─config
│ └─config.json
├─migrations
├─seeders
└─models
利用するDBをローカルに作っておく。今回は”sequelize_express_development”というdbを作成して、/config/config.json
の記載を適宜訂正した。
次にUserモデルを作成する。
はじめ下記のようにコマンド叩いていたがエラーになった。
$ sequelize model:create --name User --attributes name:string, age:integer
Task 'age:integer' is not in your gulpfile
エラーになる。$ sequelize help:model:create
を見るに、
$ sequelize model:create --name User --attributes 'name:string, age:integer'
か
$ sequelize model:create --name User --attributes name:string,age:integer
みたいな感じにしないといけない。
これで、/models/
と/migrations/
以下にファイルが作成される。便利!!
作成されたmigrationファイルからUserテーブルを作成する。
$ sequelize db:migrate
Controller, View
Controllerは/routes/user.js
、Viewは/views/users/
に作っていく。下記に完成したものを貼っておく。
var express = require('express');
const models = require('../models');
var router = express.Router();
// index
router.get('/', function(req, res, next) {
models.User.findAll().then(function(results) {
res.render('users/index', {users: results});
});
});
// new
router.get('/new', function(req, res, next) {
res.render('users/new');
});
// create
router.post('/create', function(req, res) {
models.User.create({
name: req.body.name,
age: req.body.age
}).then(function() {
res.redirect('/users');
});
});
// show
router.get('/:user_id', function(req, res) {
models.User
.findOne({where: {id: req.params.user_id}})
.then(function(result) {
res.render('users/show', {user: result});
});
});
// edit
router.get('/:user_id/edit', function(req, res) {
models.User
.findOne({where: {id: req.params.user_id}})
.then(function(result) {
res.render('users/edit', {user: result});
});
});
// update
router.put('/:user_id', function(req, res) {
models.User
.update({
name: req.body.name,
age: req.body.age
}, {
where: {
id: req.params.user_id
}
}).then(function() {
res.redirect('/users');
});
});
// destroy
router.delete('/:user_id', function(req, res) {
models.User
.destroy({
where: {
id: req.body.userId
}
}).then(function() {
res.redirect('/users');
});
});
module.exports = router;
viewは/views/users
以下に、_form.ejs, index.ejs, new.ejs, show.ejs, edit.ejs
を作った。
htmlのformでは、PUT
とDELETE
直接対応されてないので、少し工夫する必要あり。
参考:[html/css] httpのフォームでDELETEやPUTのメソッドを送る方法
で、expressで、この辺りを対応するには、npmでmethod-override
を入れる必要があるみたい。
参考:Express 4.x でmethod-overrideでput / delete メソッドを使いたいにハマる。
これですね。 expressjs/method-override
ということで、入れる。
$ npm install --save method-override
今回はこの書き方で対応した。
override using a query value
var express = require('express')
var methodOverride = require('method-override')
var app = express()
app.use(methodOverride('_method'))
として、viewでは、
<form method="post" action="/users/1?_method=put">
<button type="submit">Update!!</button>
</form>
みたいな感じで、put, delete使うことができる。
また、input type="hidden"の形式で書く場合はこちら形で対応するといけるみたい。 custom logic
ここでは、/views/users/index.ejs
と/views/users/_form.ejs
を下記に記載する。(さっと書いたので汚いですが)
<!DOCTYPE html>
<html>
<head>
<title>Sample App</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>Sequelize Express</h1>
<p>Welcome!!</p>
<% if (users) { %>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<% users.forEach(function(value, key) { %>
<tr>
<td><%= value.name %></td>
<td><%= value.age %></td>
<td><a href="/users/<%= value.id %>">Show</a></td>
<td><a href="/users/<%= value.id %>/edit">Edit</a></td>
<td>
<form name="delete<%= value.id %>" action="/users/<%= value.id %>?_method=delete" method="post">
<input type="hidden" name="userId" value="<%= value.id %>">
<a href="#" onclick="document.delete<%= value.id %>.submit()">Delete</a>
</form>
</td>
</tr>
<% }); %>
</table>
<% } %>
<a href="/users/new">create new user</a>
</body>
</html>
<%
let form_action = '#';
let form_name = '';
let form_age = '';
switch(template) {
case 'new':
form_action = '/users/create';
break;
case 'edit':
form_action = `/users/${user.id}?_method=put`;
form_name = `${user.name}`;
form_age = `${user.age}`;
break;
}
%>
<form action="<%= form_action %>" method="post">
<label for="name">Name</label>
<input type="text" name="name" value="<%= form_name %>">
<label for="age">Age</label>
<input type="number" name="age" value="<%= form_age %>">
<input type="submit" value="submit">
</form>
あとは、new,edit,show辺りのviewを作れば完成。
コードはgithubに置いたので、興味ある人いれば見てみて下さい。
mazeltov7/sequelize-express-scaffold
おまけ
Userに紐付いたCommentも作ってみる。
まずはモデルをさっと作る。
$ sequelize model:create --name Comment --attributes content:text
デフォルトでは、foreignKeyがCamelCaseになってるけど(userIdみたいな)、今回はsnake_caseに変更してやった。
migrationファイルを作って、Commentsテーブルにuser_idカラムを追加する。(今回はmigrationファイルを別途作ってColumn追加してみたかったので、テーブル作成と別にやってみた。)
$ sequelize migration:create --name AddColumnToComment
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.addColumn(
'Comments',
'user_id',
{
type: Sequelize.INTEGER,
allowNull: false
}
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.removeColumn(
'Comments',
'user_id'
);
}
};
で、次にモデル。Commentモデル側にはbelongsToを記載する。
'use strict';
module.exports = function(sequelize, DataTypes) {
var Comment = sequelize.define('Comment', {
content: DataTypes.TEXT,
user_id: DataTypes.INTEGER
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
Comment.belongsTo(models.User, {'foreignKey': 'user_id'})
}
}
});
return Comment;
};
Userモデル側にはhasMany記載する。
'use strict';
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('User', {
name: DataTypes.STRING,
age: DataTypes.INTEGER
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
User.hasMany(models.Comment, {'foreignKey': 'user_id'})
}
}
});
return User;
};
テーブル作成。
$ sequelize db:migrate
コントローラ側も修正する。includeして、getで取得してくる。
// show
router.get('/:user_id', function(req, res) {
models.User
.findOne({
where: {id: req.params.user_id}
}, {
include: [ models.Comment]
})
.then(function(userResult) {
userResult.getComments().then(function(commentResults) {
res.render('users/show', {user: userResult, comments: commentResults});
})
});
});
view側も修正。
<% if (comments) { %>
<ul>
<% comments.forEach(function(value, key) { %>
<li><%= value.content %></li>
<% }) %>
</ul>
<% } %>
で、こんな感じ。