Node.js X Rails Engine

[8日目](http://qiita.com/ogomr/items/078f9b2a7e78f2b4cc54) に続けての投稿です。
今回は [React](https://facebook.github.io/react/) and [Redux](http://redux.js.org/) を [Rails Engine](http://guides.rubyonrails.org/engines.html) でクロスさせる開発を紹介します。

Rails Engine は Rails に Webアプリケーションの部品として機能を追加できる仕組みです。
React and Redux もコンポーネントを組合せて機能を作ります。

* Rails Engine: Web Application Component Framework
* React and Redux: Web Client Component Framework

React and Redux の コンポーネント を Rails Engine でさらに コンポーネント にする仕組みだと
関心事の分離して設計できて *コンポーネント指向* が捗ります。

サンプルは[こちら](https://github.com/dddrb/rails-engine-react-example)です。

## Environment

* Ruby: v2.2.3
* Rails: v4.2.5
* React: v0.14.0
* Redux: v3.0.4


## Rails

まずは、Rails に *Virtual DOM* をレンダリングするためのサーバーサイドを構築します。

### Setup

* `rails plugin new` のコマンドで Rails Engine を作成します。
* `--mountable` のオプションで Rails の部品として開発できます。
* `foreman` は後で Node.js と Rails を起動するのに利用します。

```bash
rails plugin new rails_engine_react_example --mountable \
  --skip-javascript \
  --skip-bundle
$ cd rails_engine_react_example/
$ echo "gem 'foreman', require: false" >> Gemfile
$ bundle
```

### Making

* `engine.rb` に `generate` コマンドのデフォルトを記述します。

```ruby:lib/rails_engine_react_example/engine.rb
module RailsEngineReactExample
  class Engine < ::Rails::Engine
    isolate_namespace RailsEngineReactExample

    config.generators do |g|
      g.javascripts false
      g.stylesheets false
      g.helper false
    end
  end
end
```

* `rails generate controller` コマンドで *controller* と *view* を作ります。

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

* `routes.rb` で `main_controller` を `root` に設定します。
* `*path` は Tips の Redux Router で利用しています。

```ruby:config/routes.rb
RailsEngineReactExample::Engine.routes.draw do
  root 'main#index'
  get '*path', to: 'main#index'
end
```


* `index.html.erb` に Virtual DOM がレンダリングされる `id="main"` を記述します。

```ruby:app/views/rails_engine_react_example/main/index.html.erb
<div id="main"></div>
```

* `application_helper.rb` に *stylesheet* と *javascript* の参照先を切替えるメソッドを追加します。

```ruby:app/helpers/rails_engine_react_example/application_helper.rb
module RailsEngineReactExample
  module ApplicationHelper
    def switch_stylesheet_link_tag(path)
      path = "http://localhost:4000/assets/stylesheets/#{path}.css" if ENV["DEBUG"]
      stylesheet_link_tag(path)
    end

    def switch_javascript_include_tag(path)
      path = "http://localhost:4000/assets/javascripts/#{path}.js" if ENV["DEBUG"]
      javascript_include_tag(path)
    end
  end
end
```

* `application.html.erb` に先ほどのヘルパーのメソッドを記述します。

```html:app/views/layouts/rails_engine_react_example/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>RailsEngineReactExample</title>
  <%= switch_stylesheet_link_tag "rails_engine_react_example/application" %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
  <%= switch_javascript_include_tag "rails_engine_react_example/application" %>
</body>
</html>
```

### Run


* `rails server` コマンドで起動して `http://localhost:3000/rails_engine_react_example` にアクセスします。


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

真っ白ですね... 安心してください、 *Virtual DOM* はこれからです。


## Node.js

次は、Node.js で Virtual DOM をレンダリングするためのフロントエンドを構築します。

### Setup

* Node.js は /private/static に設置します。
* yo redux のコマンドで React and Redux が構築できます。
    * Node.js は 4000 ポートを利用します。

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

* `js` は `app` にリネームします。（`css` も *components* で記述する流れですね。）
* `package.json` ファイルはルートディレクトリにコピーします。

```
$ mv js app
$ 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"
},
```

* `webpack.config.js` を Rails のヘルパーのメソッドで参照できるように調整します。

```javascript:private/static/webpack.config.js
entry: [
  'webpack-dev-server/client?http://localhost:4000',
  'webpack/hot/only-dev-server',
  './app/index.js'
],
output: {
  path: __dirname + '/assets/',
  publicPath: 'http://localhost:4000/assets/',
  filename: 'javascripts/rails_engine_react_example/application.js',
  hot: true
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoErrorsPlugin(),
  devFlagPlugin,
  new ExtractTextPlugin('stylesheets/rails_engine_react_example/application.css')
],
```

* `webpack.production.js` を Rails の `app/assets/` に出力するように調整します。

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

### Run

* Rails Engine は Rails にマウントしている状態の `dummy` が提供されています。
* `foreman` の Procfile に Rails と Node.js の起動コマンドを追加します。
* `DEBUG=true foreman start` コマンドで起動します。

```bash
$ cd test/dummy/
$ echo "web: rails server" >> Procfile
$ echo "pack: npm start" >> Procfile
$ DEBUG=true foreman start
```

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


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

* `npm run build` コマンドで `application.js` と `application.css` が生成されます。
* `rails server` コマンドで起動します。

```
$ npm run build
$ rails server
```

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

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

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


# Tips

## Redux Router

**Rails** は *Router* もあまり関心を持たないほうが...

### Setup

* `react-router` と `redux-simple-router` を利用します。

```
$ npm install react-router --save
$ npm install redux-simple-router --save
```

### Making

コードは一部ですが Router や Link を記述します。

* `App.js` に Router を記述します。

```javascript:private/static/app/containers/App.js
<Router history={history}>
  <Route path="/" component={Home}>
    <Route path="parent" component={Parent}>
      <Route path="child" component={Child} />
      <Route path="child/:id" component={Child} />
    </Route>
  </Route>
</Router>
```

* `Home.js` に Link を記述します。

```javascript:private/static/app/components/Home.js
<dev>
  <ul>
    <li><Link to="/">Home</Link></li>
    <li><Link to="/parent">parent</Link></li>
    <li><Link to="/parent/child">child</Link></li>
    <li><Link to="/parent/child/1">child 1</Link></li>
    <li><Link to="/parent/child/2">child 2</Link></li>
    <li><Link to="/parent/child/3">child 3</Link></li>
  </ul>
</dev>
```

### Run

Link のエリアをクリックとサーバーサイドにはリクエストが送信されませんが、レンダリングされます。
URLを入力するとサーバーサイドにはリクエストが送信されますが、Rails に `*path` を設定しているので *Virtual DOM* がレンダリングされます。


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

**Redux** は状態を管理するメカニズムがあります。ページ遷移も状態として管理されます。
**Rails** は *MVC2* ですが、 *View* に状態が管理されるので *MVC* ですね。

*Enjoy Node.js X Rails Engine*