JavaScript on Node.js X Ruby on Rails


[JavaScript](https://ja.wikipedia.org/wiki/JavaScript) はWebアプリケーションの特にフロントエンドで大切な存在です。
Rails もWebアプリケーションの開発で便利な機能がたくさんあります。
この２つのメカニズムを使うのに、どちらかによせて混ぜ合わせるのではなく
それぞれに分離して、２つのメカニズムをクロスして使うほうが幸せを感じます。

[Ruby on Rails](http://rubyonrails.org/) に慣れていない、JavaScript のエンジニア向けに *View* に
[React](https://facebook.github.io/react/) と [Redux](http://rackt.org/redux/) を利用した **JavaScript** と **Rails** をクロスする手順を記します。

このサンプルは[GitHub](https://github.com/dddrb/rails-react-example)で公開しています。


## Environment

* Ruby: v2.2.3
* Rails: v4.2.5
* Node.js: v4.2.1
* React: v0.14.0
* Redux: v3.0.4
* webpack: v1.12.9
* yo: v1.5.0
* generator-redux: v0.3.1

----

## Rails new

* `rails new` のコマンドでWebアプリケーションが簡単に構築できます。

```bash
$ rails new rails-react-example
$ cd ./rails-react-example
```


## Yeoman

* [Yeoman](http://yeoman.io/) の *Generator* を利用して **Redux** を構築します。

```bash
$ npm install --global yo
$ npm install --global generator-redux
```

* [Phoenix](http://www.phoenixframework.org/) の様に **JavaScript** を `/private/static` に設置します。
* `yo redux` のコマンドで対話式にWebアプリケーションが構築できます。
* **Rails** が `3000` ポートを標準で利用するので、ここでは `4000` を設定します。

```bash
$ mkdir -p ./private/static
$ cd ./private/static
$ yo redux
? What's the name of your application? static
? Describe your application in one sentence: ...
? Which port would you like to run on? 4000
? Install dependencies? No
```


## npm

* `package.json` ファイルをルートディレクトリにコピーします。
* `npm install` コマンドで **JavaScript** のライブラリがインストールされます。

```bash
$ cd ../../
$ cp ./private/static/package.json .
$ echo /node_modules >> .gitignore
$ npm install
```

* `package.json` の `scripts` のコマンドに `cd ./private/static && ` を付加します。

```javascript:package.json
"scripts": {
  "start": "cd ./private/static && DEBUG=true node server.js",
  "build": "cd ./private/static && webpack -p --config webpack.production.js",
  "build-dev": "cd ./private/static && webpack -p"
},
```

* `npm start` のコマンドを実行すると **Node.js** の `webpack-dev-server` (内部は[express](http://expressjs.com)) でWebアプリケーションが起動します。
* `http://localhost:4000/` にアクセスすると *Welcome Home!* のサンプルが表示されます。

![node](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/framework/redux/node.png)


`view-source`:

```html
<html>
<head>
  <meta charset="utf-8">
  <title>Redux/React App</title>
  <link href="./static/app.css" rel="stylesheet" type="text/css" />
</head>
<body>

  <div id="main"></div>

  <script src="./static/bundle.js"></script>
</body>
</html>
```

これは *Redux Generator* が出力したサンプルで、`Update Title` をクリックして入力すると *Welcome* の隣の文字が変更されます。
`redux-devtools` や `react-hot-loader` など開発に便利なパッケージがバンドルされています。

まだ **Node.js** しか使っていないので、次は **Rails** も使います。


----

## Rails controller

* `rails generate controller` コマンドで **React** の *Virtual DOM* を設置する場所を作ります。

```bash
$ rails generate controller main index
```

* `routes.rb` に **Rails** のWebアプリケーション `root` を設定します。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'main#index'
end
```

* `main_helper.rb` に *Development* なら先ほどの **Node.js** のWebアプリケーションを参照するメソッドを準備します。

```ruby:app/helpers/main_helper.rb
module MainHelper
  def pack_path(path)
    url = ""
    url = "http://localhost:4000/static/" if Rails.env.development?
    url + path
  end
end
```

* `index.html.erb` に *Virtual DOM* が設置される `id="main"` を準備します。
* `app.css` と `bundle.js` を *MainHelper* の `pack_path` に設定します。

```ruby:app/views/main/index.html.erb
<div id="main"></div>
<%= stylesheet_link_tag    pack_path('app.css') %>
<%= javascript_include_tag pack_path('bundle.js') %>
```

* `webpack.config.js` の `publicPath` に *MainHelper* で指定した URL を追加します。

```javascript:private/webpack/webpack.config.js
output: {
  path: __dirname + '/static/',
  publicPath: 'http://localhost:4000/static/',
  filename: 'bundle.js',
  hot: true
},
```

## Development

* **Rails** と **Node.js** のWebアプリケーションを実行するのに [foreman](https://github.com/ddollar/foreman) を利用します。
* `Gemfile` に **foreman** を追加します。

`Gemfile`:

```bash
$ echo "gem 'foreman', require: false" >> Gemfile
```

* `Procfile` に **Rails** と **Node.js** のWebアプリケーションの起動コマンドを追加します。

`Procfile`:

```bash
$ echo "web: rails server" >> Procfile
$ echo "pack: npm start" >> Procfile
```

* `bundle` コマンドを **Ruby** のライブラリをインストールします。
* `foreman start` コマンドでWebアプリケーションを起動します。

```bash
$ bundle
$ foreman start
```

`http://localhost:3000/` にアクセスすると **Redux** のサンプルが表示されます。

![development](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/framework/redux/development.png)

**StyleSheet** と **JavaScript** が `http://localhost:4000/` を参照しています。

`view-source`:

```html
<div id="main"></div>
<link rel="stylesheet" media="screen" href="http://localhost:4000/static/app.css" />
<script src="http://localhost:4000/static/bundle.js"></script>
```

**Rails** が出力したわずかな *View* に *Virtual DOM* で *View* を足しているので
見た目や、性能は `4000` ポートの **Node.js** のWebアプリケーションと同じです。
もちろん `redux-devtools` や `react-hot-loader` が使えるので開発が捗ります。


----

## Production

*Production* では `redux-devtools` や `react-hot-loader` は不要なので、[webpack](https://webpack.github.io/) でまとめます。

* *Asset Precompile* に `app.css` と `bundle.js` を追加します。

```ruby:config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(app.css bundle.js)
```

* `webpack.production.js` を `vendor/assets/` に出力されるように編集します。

```javascript:private/static/webpack.production.js
output: {
  path: __dirname + '../../../vendor/assets/',
  publicPath: '/static/',
  filename: 'javascripts/bundle.js',
},
plugins: [
  new webpack.NoErrorsPlugin(),
  devFlagPlugin,
  new ExtractTextPlugin('stylesheets/app.css')
],
```

* `npm run build` コマンドで `app.css` と `bundle.js` が生成されます。
* `rake assets:precompile` コマンドを `app.css` と `bundle.js` を *Precompile* します。
* `rails s -e production` コマンドでWebアプリケーションを起動します。

```bash
$ echo /public/assets >> .gitignore
$ echo /vendor/assets >> .gitignore
$ npm run build
$ RAILS_ENV=production rake assets:precompile
$ RAILS_SERVE_STATIC_FILES=o SECRET_KEY_BASE=o rails s -e production
```

`http://localhost:3000/` にアクセスすると **Redux** のサンプルが表示されます。

![production](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/framework/redux/production.png)

**StyleSheet** と **JavaScript** が `/assets/` を参照しています。

`view-source`:

```html
<div id="main"></div>
<link rel="stylesheet" media="screen" href="/assets/app-5edef2394b419bead200965dfdae792e54801bdd97f057758a8bc0a72f25685d.css" />
<script src="/assets/bundle-66f3247c874d05e4bf42eb08d4fc66d88bd796bdbca894259cd898078a39bf97.js"></script>
```

これで **JavaScript on Node.js X Ruby on Rails** の基本の準備ができました。
*View* は **React** に任せて **Rails** は *View* にあまり関心を持たないほうがいいですね。



----

# Tips

見た目が寂しなら [Material-UI](http://www.material-ui.com/)で簡単に装飾できます。

```bash
$ npm install material-ui --save
```

* *RaisedButton* でボタンをいい感じに仕上げます。

```javascript:private/static/js/components/Home.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as HomeActions from '../actions/HomeActions';
import styles from '../../css/app.css';
import RaisedButton from 'material-ui/lib/raised-button';

class Home extends Component {
  render() {
    const {title, dispatch} = this.props;
    const actions = bindActionCreators(HomeActions, dispatch);
    return (
      <main>
        <h1 className={styles.text}>Welcome {title}!</h1>
        <RaisedButton label='Update Title' onClick={e => actions.changeTitle(prompt())} />
      </main>
    );
  }
}

export default connect(state => state.Sample)(Home)
```

![material-ui](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/framework/redux/material-ui.png)

**JavaScript** のライブラリの [npm](https://www.npmjs.com/) を **Ruby** のライブラリの [Gem](https://rubygems.org/) に作り替えて使う方法もよいですが
関心事を分離して開発することが幸せと感じます。

*Enjoy JavaScript on Node.js X Ruby on Rails*