冬休みにRailsとVueでWebサービス作ったよ

冬休みがスーパー長かったのでクリスマスに買ってもらった[Vue入門本](https://www.amazon.co.jp/Vue-js%E5%85%A5%E9%96%80-%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E5%AE%9F%E8%B7%B5%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%81%BE%E3%81%A7-%E5%B7%9D%E5%8F%A3-%E5%92%8C%E4%B9%9F/dp/4297100916)を斜め読みしながら、覚えられないコマンドメモサービス的なのを作りました。Railsのviewにちょろっと動きをつけた程度になります。
[Commandware](https://commandware.herokuapp.com/)
ソース: https://github.com/yubachiri/commandware
この記事に書いてあるコードから動かなそうな雰囲気を感じたら動くものが置いてあるgithubを見てみてください。

あと結構適当なことを書いていると思います。適宜ご指摘いただけると幸いです。

## やりたかったこと
- RailsでVue使いたかった
- ついでにVuexも使ってみたかった
- paneやりたかった(pivotal trackerみたいなの)(やりたかったことの実に10割以上を占める)
- 「git なかったことにする」をもう二度とググりたくなかった

## 書くこと
- 作ったものの解説
- Vue楽しい

Railsのviewにちょっと動きをつける程度のものでピンとくるものが見つけられなかったので書こうかと思いました
あとVue入門本よかった

## 準備

まず、rails newとか、DB設計と簡単なUI設計と一通りサービスが回るCRUDを実装しておきます。と言ってもコマンドゥェァは超質素。ER図はこんな感じです。いずれも記載同順で 1 - N です。[wwwsqldesigner](http://ondras.zarovi.cz/sql/demo/?keyword=default)で作りました。
<img width="497" alt="スクリーンショット 2019-01-18 0.39.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/255661/04c6941c-e6ba-ac42-95b2-e9ab614e1edb.png">
UIもこんな感じでちゃちゃっと手書き。迷いが見られますね。
![iOS の画像 (6).jpg](https://qiita-image-store.s3.amazonaws.com/0/255661/f6cb6e7b-ec64-9609-cbfa-270fd57a3cdb.jpeg)

そんで僕はnewする時にvue導入していなかったので、[こちらの記事(https://qiita.com/cohki0305/items/582c0f5ed0750e60c951)](https://qiita.com/cohki0305/items/582c0f5ed0750e60c951)に書いてあるような感じでvueを導入しました。
あと悲しみを背負ったのでTurbolinksは無効にしました。https://qiita.com/kazz187/items/12737363d62b9c91993c

## 中身概要

jsのファイル構造はこんな感じ。app直下です。使ってないものを消してないのには目をつむってください。
![スクリーンショット 2019-01-18 0.57.05.png](https://qiita-image-store.s3.amazonaws.com/0/255661/42e43fb5-8c54-4b31-b0a2-f585d9ba72ad.png)

[materialize](https://materializecss.com/)のgridシステムの恩恵にあずかりつつ、まずはサイドバーとメイン部分を`col.s3`と`col.s9`で分けました。Vueを書く`commandware.js`も読み込んでおきます。

```slim:index.html.slim(以下slimと記載)
= javascript_pack_tag 'commandware'

#commandware
  .row
    / サイドバー(flow一覧)
    .col.s3
    / メイン
    .col.s9
```

`commandware.js`はこんな感じ。
Railsにリクエスト送る`axios`(https://github.com/axios/axios) は`yarn add axios`で入れた。

```javascript:commandware.js
import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex';
import store from '../store/index';
import axios from 'axios';

Vue.use(Vuex);

axios.defaults.headers.common = {
  'X-Requested-With': 'XMLHttpRequest',
  'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#commandware',
    store,

    data: {略},
    computed: {略},
    methods: {略},

  })
})

```

以下、ポイントを見ていきます。

## サイドバーについて

ここですね。

<img width="311" alt="スクリーンショット 2019-01-18 1.13.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/255661/80d57c24-d67b-5157-3cb1-7fe443de79bc.png">

コードはこちら

```slim:slim
    / サイドバー（Flow一覧）
    .col.s3
      .collection.with-header
        .collection-header
          | Flows
        .collection-item.new-flow-section
          form @submit.prevent="postFlow"
            input.new-flow-input type="text" placeholder="add by push Enter" v-model="newFlow"
        .collection-item.clickable-item {v-for="flow in flows" :key="flow.id" @click="commandsDisplay(flow.id)"} = "{{flow.name}}"
```

commandware.jsのうち関係ある部分のみピックアップしたもの

```javascript:commandware.js
document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#commandware',
    store,
    data: {
      newFlow: '',
    },
    computed: {
      flow: function () {
        return store.state.flow
      },
      flows: function () {
        return store.state.flows
      },
    },
    methods: {
      postFlow() {
        axios.post(`/flows.json`, 
          flow: {
            name: this.newFlow
          }
        })
          .then(response => {
            this.newFlow = ''
            store.commit('addFlow', response.data)
          })
          .catch(error => {
            alert('エラーが発生しました。')
          })
      },
    },
  })
})
```

`/flows.json`にpostすると呼ばれるもの

```ruby:flows_controller.rb
class FlowsController < ApplicationController
  before_action :set_flow, only: [:show, :edit, :update, :destroy]

  # 略

  def create
    @flow = Flow.new(flow_params.merge(user: current_user))

    respond_to do |format|
      if @flow.save
        format.html { redirect_to @flow, notice: 'Flow was successfully created.' }
        format.json { render :show }
      else
        format.html { render :new }
        format.json { render json: @flow.errors, status: 500}
      end
    end
  end

  # 略

  private

  def set_flow
    @flow = Flow.find(params[:id])
  end

  def flow_params
    params.require(:flow).permit(:name, :description)
  end
end
```

というわけで長くなりましたが、この部分の解説をしてみたいと思います。

まずこれ、新しいflowの追加フォーム `form @submit.prevent="postFlow"` ですが、`@submit.prevent="postFlow"`では@submit(=v-on:submit)の[イベントを止め](https://jp.vuejs.org/v2/guide/events.html#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E4%BF%AE%E9%A3%BE%E5%AD%90)、自身で定義したハンドラ(`postFlow`)に投げます。submit自体はenter押下時に実行されますね。[参考](https://qiita.com/kimullaa/items/c60a5a229b9060177a48)

呼ばれる`postFlow`ではaxiosでRailsにpostリクエストをパラメータを添えて投げています。

```javascript:post箇所
axios.post(`/flows.json`, {
  flow: {
    name: this.newFlow
  }
})
```

`this`はVueインスタンス自身、`newFlow`はVueインスタンスに定義したdataです。
newFlowにはform内の`input.new-flow-input type="text" placeholder="add by push Enter" v-model="newFlow"`の`v-model="newFlow"`でバインディングしているので、この欄に入力した値が入ります。
Railsからレスポンスが返ってくると、

```javascript:post箇所続き
axios.post(`/flows.json`, {
  flow: {
    name: this.newFlow
  }
})
  .then(response => {
    this.newFlow = ''
    store.commit('addFlow', response.data)
  })
    .catch(error => {
    alert('エラーが発生しました。')
  })
```

`then`以降で受け取って処理していきます。`response.data`にRailsがreturnしたものが入っています。今回の場合jsonです。
`this.newFlow = ''`で入力欄を空にして、`store.commit('addFlow', response.data)`ではVuexのmutationを呼び出し、Vuexが管理しているデータをいじります。この辺のフローは公式で学びましょう。https://vuex.vuejs.org/ja/
ただ、今回の規模でVuexを使うのは冗長かと思います。

```javascript:index.js(vuexを管理する元ファイル)
import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

Vue.use(Vuex)

const state = {
  flows: gon.flows,
  flow: {},
}

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})

```


```javascript:mutations.js
export default {
  addFlow(state, flow) {
    state.flows.push(flow)
  },
}
```

`gon`はRailsからjsに値を渡す時に便利なやつです。https://github.com/gazay/gon
今回は`index.html.slim`を表示するアクション内で`gon.flows = current_user.flows`という形でログイン中のユーザーが持っているflowを入れてあるので、初期ではこれを持っています。
axiosのpostが成功したら、追加したflowの情報がmutationを介してvuexのflows配列にpushされるわけですね。長い。
でサイドバーではこのflowsを一覧表示しています。flowsが更新されると同時に一覧表示も更新されます。

```slim:slim
    .collection-item.clickable-item {v-for="flow in flows" :key="flow.id" @click="commandsDisplay(flow.id)"} = "{{flow.name}}"
```

ここまでがサイドバーに表示されているものの一連の流れになります。

一旦ここまでにします。メイン部分は後日追記します。