Sinatra with Vue.js で API と通信するアプリケーションを作ろう

## これはなに

Sinatra で echo サーバを、 Vue.js でフロントを構築しようというチュートリアルです。
この記事では Sinatra, Vue.js どちらも触れて、 `hello world` を表示するところまで行います。
「ウェブアプリケーションを作りたいけど何からやっていいかわからない」、「Vue.js やりたいけど、フロントエンド周りがよくわからない」という方向けの記事になります。

入門にしては内容が多めですが、どれも Web開発をするためには便利なツールなので一度自分の手で実行してみることを推奨します。

全てのコードはこちらのリポジトリにあります。
https://github.com/Asuforce/sinatra_with_vue

### 取り扱うソフトウェア

雑に役割を書いてますが、ここで理解できなくても大丈夫です。
チュートリアルを進めながら学んでいきましょう。わからない部分はまずはリンクしてある公式サイトを見ることがオススメです。
全て　18/3/18 時点の最新のバージョン、シンタックスになるように努力しました。（改善点大歓迎です。

- [Sinatra](http://sinatrarb.com/)
  - Webアプリケーションフレームワーク
  - さくっとサーバーサイドが作れます
- [Bundler](http://bundler.io/)
  - gemマネージャー
  - gem を管理できます
- [Slim](https://github.com/slim-template/slim)
  - テンプレート言語
  - 楽に html が書けるオススメの gem です
- [RSpec](http://rspec.info/)
  - サーバーサイドのテストツール
  - Webアプリケーションの振る舞いをテストします
- [npm](https://www.npmjs.com/)
  - jsパッケージマネージャー
  - js のライブラリを管理できます
- [Webpack](https://webpack.js.org/)
  - jsバンドラー
  - js ファイルを任意の設定でまとめてくれます
- [Vue.js](https://jp.vuejs.org/)
  - jsプログレッシブフレームワーク
  - UI を構築するためのフロントエンドライブラリです

### 事前準備

以下のソフトウェアを取り扱うので予めインストールしておいてください。バージョンは 18/3/18 のものになります。
あと簡単な Linux コマンドを多用するので、お好きなターミナルを起動しておいてください。

- [Ruby](https://www.ruby-lang.org/ja/) (2.5.0)
- [Node.js](https://nodejs.org/) (9.8.0)

## Sinatra でサーバサイドを作る

### init 

git 管理したい人は、ここで `git init` しておくと捗ります。

```sh
mkdir myapp
cd myapp
bundle init
echo gem \"sinatra\" >> Gemfile
bundle install -j4 --path vendor/bundle
touch app.rb
```

### hello, world

```rb:app.rb
require "sinatra/base"

class MyApp < Sinatra::Base
  get "/" do
    'hello, world!'
  end

  run! if app_file == $0 # ファイルを読み込むとサーバを実行する
end
```

```sh
bundle exec ruby app.rb
```

http://localhost:4567 にアクセスして `Hello world!` が見えたら正常です。
Mac の場合以下のコマンドが便利です

```sh
open http://localhost:4567
```

git で管理している人は`.bundle`, `venodr` を `.gitignore` に追加しましょう。

```sh
echo '/.bundle/\n/vendor/' > .gitignore
```

### 開発効率を向上させる

ここまでのアプリケーションでローカルサーバを立ち上げてから、 `hello, world` を変えて再アクセスしてみましょう。レスポンスが `hello, world` が変更されない筈です。現状はサーバの設定がリロードを実行しないと読み込まれません。

これでは効率が悪いので以下の設定で改善します。

`sinatra-contrib` を `Gemfile` に追加します。
`sinatra-contrib` には他にも便利なライブラリがあるので下記を参考にしてみると発見があると思います。

[Sinara-Contribまとめ](https://qiita.com/ma2saka/items/8e377fdc5f29bdb0ea66)

```sh
echo gem \'sinatra-contrib\' >> Gemfile
bundle install
```

`app.rb` に以下を書き加えます。

```diff:app.rb
 require "sinatra"
+require 'sinatra/reloader'

 class MyApp < Sinatra::Application
+  configure :development do
+    register Sinatra::Reloader
+  end
+
   get "/" do
```

これでローカルサーバを起動しながら、ガンガン変更ができるようになります。

### Test を書く

簡単なアプリケーションができたので、テストも作成してみましょう。

ref: http://recipes.sinatrarb.com/p/testing/rspec
RSpec を使います。下記を `Gemfile` に追加します

```diff:Gemfile
 gem "sinatra"
 gem 'sinatra-contrib'
+
+group :test do
+  gem 'rspec'
+  gem 'rack-test'
+end
```

`bundle install` して `rspec --init` を実行します。

```sh
bundle install
bundle exec rspec --init
```

`spec/spec_helper.rb` を下記のように書きましょう。

```rb:spec/spec_helper.rb
require 'rack/test'
require 'rspec'

ENV['RACK_ENV'] = 'test'

require File.expand_path '../../app.rb', __FILE__

module RSpecMixin
  include Rack::Test::Methods
  def app
    MyApp
  end
end

RSpec.configure { |c| c.include RSpecMixin }
```

`spec/app_spec.rb` を作成しテストを記述します。テストの内容はページのレスポンスと body の内容を確認するものです。

```sh
touch spec/app_spec.rb
```

```rb:spec/app_spec.rb
describe "My Sinatra Application" do
  before :all do
    get '/'
  end

  it "should allow accessing the home page" do
    expect(last_response).to be_ok
  end

  it "should discribe message" do
    expect(last_response.body).to eq('hello, world!')
  end
end
```

テストが書けたら以下を実行して、実際に RSpec を実行してみましょう。
緑色の文字で `2 example, 0 failures` と出たら成功です。

```sh
bundle exec rspec
```

### View を表示する

[Slim](https://github.com/slim-template/slim) というテンプレートエンジンを使います。Ruby には標準ライブラリの [ERB](https://docs.ruby-lang.org/ja/latest/class/ERB.html) を用いる事が多いですが、今回はよりシンプルに記述できる Slim を使って View を作成します。

Slim の記法は以下の記事が大変参考になります。
[マークアッパー的 Slim 入門21の手引き](http://blogged.e2esound.com/2013/07/22/21_tips_to_use_slim_for_markup_engineer/)

```sh
echo gem \"slim\" >> Gemfile
bundle install
mkdir views
touch views/index.slim
```

```slim:views/index.slim
doctype html
html
  head
    meta charset="UTF-8"
    title MyApp
  body
    div id="app"
      h1 hello, world
```

同じファイルを html で出力してみましょう。

```sh
bundle exec slimrb -p views/index.slim | cat
```

このような出力結果が得られます。Slim の方がスッキリした印象があると思います。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>MyApp</title>
  </head>
  <body>
    <div id="app">
      <h1>
        hello, world
      </h1>
    </div>
  </body>
</html>
```

アプリケーションのコードを変更して View ファイルを表示してみましょう。

```diff:app.rb
 require "sinatra/reloader"
+require "slim"

 class MyApp < Sinatra::Base
   configure :development do
     register Sinatra::Reloader
   end

   get "/" do
-    'hello, world!'
+    slim :index
   end
```

H1 タグで大きく表示されるようになっていたら成功です。

### データの受け渡し

ここまでで静的なページを作ることができました。
次はアプリケーションからのパラメータを表示する例です。

```diff:app.rb
 get "/" do
+  @message = 'This is MyApp'
   slim :index
 end
```

```diff:views/index.slim
 body
   div id="app"
-    h1 hello, world
+    h1 = @message
```

ブラウザをリロードして `This is MyApp` と表示されたら成功です。
このままだとテストが失敗してしまうので、メッセージの内容を正しく書き直してテストをパスするようにします。

```diff:spec/app_spec.rb
   it "should discribe message" do
-    expect(last_response.body).to eq('hello, world!')
+    expect(last_response.body).to include('This is MyApp')
   end
```

再度テストを実行してパスすることを確認してください。

## フロントエンドの作成

### Vue.js の導入

予め  のインストールをお願いします。
Node.js がインストールできたら以下を実行してください。`package.json` が作成されます。 

```sh
# package.json を初期化します
npm init -y

# i = install
# -D = devDependencies に追加
npm i -D webpack webpack-cli

# Vue.js のインストール
npm i vue
```

`./node_modules` にモジュールがインストールされます。
このディレクトリは ignore します。

```sh
echo /node_modules/ >> .gitignore
```

今回は以下の設定が `package.json` にあれば十分です。（特にデフォルトから編集する必要はありません。が以下をベースに話を進めます。

```json:package.json
{
  "dependencies": {
    "npm": "^5.7.1",
    "vue": "^2.5.16"
  },
  "devDependencies": {
    "webpack": "^4.1.1",
    "webpack-cli": "^2.0.12"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
```

### webpack の準備

ref: [最新版で学ぶwebpack 4入門 – JS開発のモジュールバンドラ](https://ics.media/entry/12140)
Vue.js の導入を行なった際に [webpack](https://webpack.js.org/) も導入しました。
これは高度な Web アプリケーションの構築のためのモジュールバンドラーです。js, css の minify をビルド時に実行することも可能です。
参考記事がとてもわかりやすいので、併せてご覧ください。

早速、webpack の設定を行いましょう。

```sh
touch webpack.config.js
```

以下の設定で ok です。
`entry` はデフォルトの `src/index.js` を使うので書きません。`output` は Sinatra が js ファイルを読み込む `public` 以下に作ります。

```js:webpack.config.js
const path = require('path')

module.exports = {
  output: {
    path: path.join(__dirname, 'public', 'js'),
    filename: 'main.js'
  },
  resolve: {
    alias: {
      'vue': 'vue/dist/vue.common.js'
    }
  }
};
```

ファイルとディレクトリなども作成しておきましょう。

```sh
mkdir src
touch src/index.js

mkdir public
touch public/.gitignore
```

### Vue.js を記述する

早速、 Vue.js を使ってデータを表示していきましょう。

`index.slim` を以下のように設定します。

`v-text` はテキストコンテンツを表示するためのディレクティブです。よく `{{message}}` このような mustache 記法が使われる事が多いですが、DOM が構成された後に画面に描画されてしまうのを防ぐために `v-text` を使っています。
この問題は DOM が描画される前に js ファイルが読み込まれると発生します。footer で読み込んでいるのは header だと先述の問題が発生しやすいためです。知見がある方はこの現象の回避方法をご教授いただければと思います。

```diff:views/index.slim
   body
     div id="app"
-      h1 = @message
+      h1 v-text="message"
+  footer
+    script type="text/javascript" src="/js/main.js" charset="utf-8"
```

js ファイルはこのように記述します

```js:src/index.js
Vue = require('vue')

new Vue({
  el: '#app',
  data: {
    message: 'Hello world from Vue.js'
  },
});
```

ここまでできたら build をしましょう。npm script を活用します。
`--mode` は Webpack4 からの機能になり、`development`, `production` が選べます。

```diff:package.json
     "webpack-cli": "^2.0.11"
   },
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "build": "webpack --mode development"
   }
```

以下のコマンドを実行して、ビルドを行います。

```sh
npm run build
```

build が終わったら `public/js/main.js` を確認してみましょう。Vue.js 本体を含む諸々が1個のファイルになってるのがわかります。
確認できたらローカルサーバを立ち上げて確認しましょう。画面に `Hello world from Vue.js` と表示されたら成功です。

この変更によって再度テストが落ちるようになっています。画面の描画は Vue.js が担うようになったので RSpec のテストの範疇を超えました。実際はフロントエンドテストフレームワークを使ってテストをするのが望ましいですが、今回は割愛させていただきます。

対象の部分を削除して、テストを通します。

```diff:spec/app_spec.rb
   it "should allow accessing the home page" do
     expect(last_response).to be_ok
   end
-
-  it "should discribe message" do
-    expect(last_response.body).to include('This is MyApp')
-  end
 end
```

## まとめ

ここまでの作業で、Webアプリケーションの仕組みに触れながら、最新の環境を作成できるようになりました。
ここで作ったアプリケーションを使って自分なりの工夫をしてみるとより理解が深まると思います。