Express.js(ejs) + Sequelize(MySQL)でrailsのscaffoldアプリを作る

Sequelizeの挙動を確認するために、`rails generate scaffold`でできるサンプルアプリをSequelizeとexpress.jsで作ってみる。
今回やるのは、`rails g scaffold user name:string age:integer`した結果と同じようなものを作る。
コレ↓ (下はrailsのscaffoldで作ったもの)
![Kobito.iPf3wC.png](https://qiita-image-store.s3.amazonaws.com/0/20014/a2f696ee-99e7-c79c-3d65-4b912c6209d9.png "Kobito.iPf3wC.png")

Sequelizeはこちら。
https://github.com/sequelize/sequelize
ドキュメントはこちら。
http://docs.sequelizejs.com/en/v3/

# セットアップ

```bash
$ 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

```bash
$ npm install -g sequelize-cli
```

# Model, Migration

ここからmodel, migrationをざっと作っていく。
この辺りの流れはこちらの記事がわかりやすかった。
参考： [nodejs + express + sequelize webサーバ最小構成](http://qiita.com/yu-sa/items/d8edad8d9a91d0ddeb12)
また、あんまりドキュメントが詳しく無いけど、`$ sequelize help:[command name]`みたいな感じでhelp見ると詳しく書いてるのでオススメ。

```bash
$ sequelize init
```

これで、下記のように作成される。`/config/config.json`にDBの情報が記載されている。

```
├─config
│　└─config.json
├─migrations
├─seeders
└─models
```

利用するDBをローカルに作っておく。今回は”sequelize_express_development”というdbを作成して、`/config/config.json`の記載を適宜訂正した。

次にUserモデルを作成する。
はじめ下記のようにコマンド叩いていたがエラーになった。

```bash
$ sequelize model:create --name User --attributes name:string, age:integer
Task 'age:integer' is not in your gulpfile
```
エラーになる。`$ sequelize help:model:create`を見るに、

```bash
$ sequelize model:create --name User --attributes 'name:string, age:integer'
か
$ sequelize model:create --name User --attributes name:string,age:integer
```

みたいな感じにしないといけない。
これで、`/models/`と`/migrations/`以下にファイルが作成される。便利！！

作成されたmigrationファイルからUserテーブルを作成する。

```bash
$ sequelize db:migrate
```

# Controller, View
Controllerは`/routes/user.js`、Viewは`/views/users/`に作っていく。下記に完成したものを貼っておく。

```/routes/user.js
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のメソッドを送る方法](http://portaltan.hatenablog.com/entry/2015/07/22/122031)

で、expressで、この辺りを対応するには、npmで`method-override`を入れる必要があるみたい。
参考：[Express 4.x でmethod-overrideでput / delete メソッドを使いたいにハマる。](http://chaika.hatenablog.com/entry/2015/10/06/183604)
これですね。　[expressjs/method-override](https://github.com/expressjs/method-override)
ということで、入れる。

```bash
$ npm install --save method-override
```

今回はこの書き方で対応した。
[override using a query value](https://github.com/expressjs/method-override#override-using-a-query-value)

```app.js
var express = require('express')
var methodOverride = require('method-override')
var app = express()

app.use(methodOverride('_method'))
```
として、viewでは、

```view内のformHTML
<form method="post" action="/users/1?_method=put">
  <button type="submit">Update!!</button>
</form>
```

みたいな感じで、put, delete使うことができる。
また、input type="hidden"の形式で書く場合はこちら形で対応するといけるみたい。 [custom logic](https://github.com/expressjs/method-override#custom-logic)

ここでは、`/views/users/index.ejs`と`/views/users/_form.ejs`を下記に記載する。（さっと書いたので汚いですが:bow:）

```/views/users/index.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>

```

```/views/users/_form.ejs
<%
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を作れば完成。:+1: 

![Kobito.mOhZ64.png](https://qiita-image-store.s3.amazonaws.com/0/20014/9a14dbff-18d5-dbc8-05a3-ddde6933bb7a.png "Kobito.mOhZ64.png")

コードはgithubに置いたので、興味ある人いれば見てみて下さい。
[mazeltov7/sequelize-express-scaffold](https://github.com/mazeltov7/sequelize-express-scaffold)

# おまけ
Userに紐付いたCommentも作ってみる。
まずはモデルをさっと作る。


```bash
$ sequelize model:create --name Comment --attributes content:text
```

デフォルトでは、foreignKeyがCamelCaseになってるけど（userIdみたいな）、今回はsnake_caseに変更してやった。
migrationファイルを作って、Commentsテーブルにuser_idカラムを追加する。(今回はmigrationファイルを別途作ってColumn追加してみたかったので、テーブル作成と別にやってみた。)

```bash
$ sequelize migration:create --name AddColumnToComment
```

```/migrations/201732323-AddColumnToComment.js
'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を記載する。

```/models/comment.js
'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記載する。

```/models/user.js
'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;
};
```

テーブル作成。

```bash
$ sequelize db:migrate
```

コントローラ側も修正する。includeして、getで取得してくる。

```/routes/user.js
// 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側も修正。

```/views/users/show.ejs
    <% if (comments) { %>
      <ul>
        <% comments.forEach(function(value, key) { %>
          <li><%= value.content %></li>
        <% }) %>
      </ul>
    <% } %>

```

で、こんな感じ。

![Kobito.xZpc4l.png](https://qiita-image-store.s3.amazonaws.com/0/20014/63a96058-a6fa-ae2c-b9ef-7ebd2984b346.png "Kobito.xZpc4l.png")