標準入力でつまずいたところ

n = gets.chomp

getsとは、一行分の文字列を取得することができます。 chompとはStringクラスのメソッドで、文字列の末尾から改行コードを取り除いた文字列を返します。


t = s.split

splitとすると、スペース区切りでデータを分割してtに配列としてデータをいれることができる。

ex ['1', '2', '3', '4']


t = s.split.map(&:to_i)

map(&:to_i)はmap{ |n| n.to_i }の省略形で、すべての要素を整数に変換して配列で受け取ることができます。

ex [1, 2, 3, 4]


n = gets.to_i

arr = Array.new(n)
n.times { |i| arr[i] = 'aaa' }

puts arr.join(' ')

Array.newはnに入力された数字分の配列を用意。 timesでarr[0]~arr[2]にaaaが配列に入る。 puts arrだと aaa

aaa

aaa のように出力されるため、.join(' ')をすることで改行をなくす。

ActiveSupport::Concernを使ってエラーハンドリング処理

ActiveSupport::Concern

ActiveSupport:Concernは、モジュールであり、このモジュールをextendすると、次の機能を利用できるようになる。

  • ブロック渡しができるincludedメソッド
  • クラスメソッドの定義を容易にするclass_methodsメソッド
  • モジュール間の依存関係の解決


rescue_from

例えば、Article.find(params[:id])のようなコードはfindメソッドでレコードが見つからなかった場合、ActiveRecord::RecordNotFound例外が発生します。このActiveRecord::RecordNotFoundをコントローラの基底クラスがキャッチするとステータスコード404 Not Found」を返します。


エラーハンドリング

下のコードで、rescue_fromを利用したエラーハンドリングを実装しています。

ActiveRecord::RecordNotFoundが発生した時にはrender_404をStandardErrorが発生した時にはrender_500というアクションが実行されます。

アプリケーション全体の振る舞いとして特定の例外に対する挙動を指定したい場合はrescue_fromというクラスメソッドを用いることで実現できる。



実装

module Api::ExceptionHandler
  extend ActiveSupport::Concern

  included do
    rescue_from StandardError, with: :render_500
    rescue_from ActiveRecord::RecordNotFound, with: :render_404
  end

  private

  def render_400(exception = nil, messages = nil)
    render_error(400, 'Bad Request', exception&.message, *messages)
  end

  def render_404(exception = nil, messages = nil)
    render_error(404, 'Record Not Found', exception&.message, *messages)
  end

  def render_500(exception = nil, messages = nil)
    render_error(500, 'Internal Server Error', exception&.message, *messages)
  end

  def render_error(code, message, *error_messages)
    response = {
      message: message,
      errors: error_messages.compact
    }

    render json: response, status: code
  end
end

Vuexを使ってタスク表示と追加機能を実装

store/index.js

import Vue from "vue";
import Vuex from "vuex";
import axios from '../plugins/axios'

①
Vue.use(Vuex);

export default new Vuex.Store({

②
  state: {
    tasks: []
  },
③
  getters: {
    tasks: state => state.tasks
  },
④
  mutations: {
    setTasks(state, tasks) {
      state.tasks = tasks
    },
    addTask(state, task) {
      state.tasks.push(task)
    }
  },
⑤
  actions: { 
        getTasks({ commit }) {
          axios.get('tasks')
            .then(res => {
              commit('setTasks', res.data)
            })
            .catch(err => console.log(err.response));
        },
        createTask({commit}, task) {
          return axios.post('tasks', task)
            .then(res => {
            commit('addTask', res.data)
          })
        }
      }
})

①Vue.use(Vuex);と書くことで指定したプラグインをVue全体で使用できる


②stateを使うことで、vue.js全体で使えるグローバル変数を用意できます。今回は表示するべきタスクを格納する場所を作っておきます。これは、入力したタスクをどんどん変数tasksの配列の要素として追加していきます。※pages/index.vueの data()オブジェクト内のtasks: []はいらないので削除します。


③アロー関数で書いています。 ゲッターは第1引数として、state を受け取ります。tasksという関数を定義して、上のstateオブジェクトを引数にとっており、state.tasksとすることで、tasksの値が取れる。これをすることで、vuex側でtasks一覧を返す処理ができるようになります。pages/index.vue側でtasksと書いてあげることで、タスク一覧を取得することができます。

mapGettersを使ってgettersの値を受け取る。 store/index.jsで記載したgettersの値をpages/index.vueで受け取る方法に、mapGettersを使った受け取り方がある。


④mutationとは、mutationの中でしかstateの内容を変えられないようにする。つまりはmutationの中以外でもstateの内容を変えることができるが、mutationの中のみstateの内容を変えると制約する。第一引数はstate。mutationを呼び出す時はcommitを使い〜.commitとパッと見た瞬間にmutationを使って書き換えているんだなとわかる。

また、

setTasks(state, tasks)

この引数のtasksは、stateの中のtasksではないので注意!

getTasks({ commit }) {
          axios.get('tasks')
            .then(res => {
              // setTasks(state, tasks)のtasksにres.dataを入れているんだな。
              commit('setTasks', res.data)
            })
            .catch(err => console.log(err.response));
        },

後ほど出てくるactionsの非同期処理で、res.dataでタスク一覧を取得するための引数である。


⑤actionsもmutationsのようにstateを更新するために使用されるのですが、大きく2つの違いがあります。

  • actionsはstateを直接変更するのではなくmutationsを経由して(commit)stateを更新する
  • actionsには非同期の処理を入れることができる

actionsの中でcommitの引数にmutationsのメソッドが入ります。

getTasks({ commit }) 

({ commit })は引数分割束縛。context.commitと書く必要があるが、このように記述することでcommitと省略できる。他にもcontext.state,context.getters,context.dispatchなどがある。

pages/index.vue

<template>
  <div>
    <div class="d-flex">
      <div class="col-4 bg-light rounded shadow m-3 p-3">
        <div class="h4">TODO</div>
        <div v-for='task in tasks'
             :key='task.id'
             :id="'task-' + task.id"
             class='bg-white border shadow-sm rounded my-2 p-4'
             @click="handleShowTaskDetailModal(task)">
          <span>{{ task.title }}</span>
        </div>
        <button class="btn btn-secondary" @click="handleShowTaskCreateModal">タスクを追加</button>
      </div>
    </div>
    <div class="text-center">
      <router-link :to="{ name: 'TopIndex' }" class="btn btn-dark mt-5">戻る</router-link>
    </div>
    <transition name="fade">
      <TaskDetailModal v-if="isVisibleTaskDetailModal"
                      @close-modal="handleCloseTaskDetailModal"
                      :task="taskDetail"
                        />
    </transition>
    <transition>                   
      <TaskCreateModal v-if="isVisibleTaskCreateModal"
                       @close-modal="handleCloseTaskCreateModal"
                       @create-task="handleCreateTask"
                        />
      </transition>
  </div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex' 
import TaskDetailModal from './components/TaskDetailModal'
import TaskCreateModal from './components/TaskCreateModal'

export default {
  components: {
    TaskDetailModal,
    TaskCreateModal
  },
  name: 'TaskIndex',
  data() {
    return {
      // tasks: [],
      taskDetail: {},
      isVisibleTaskDetailModal: false,
      isVisibleTaskCreateModal: false
    }
  },
  computed: {
    ...mapGetters([ 'tasks' ])
  },
  created() {
    this.getTasks();
  },
  methods: {
    ...mapActions([
      'getTasks',
      'createTask'
    ]),
    handleShowTaskDetailModal(task) {
      this.isVisibleTaskDetailModal = true;
      this.taskDetail = task;
    },
    handleCloseTaskDetailModal() {
      this.isVisibleTaskDetailModal = false;
      this.taskDeatil = {};
    },
    handleShowTaskCreateModal(task) {
      this.isVisibleTaskCreateModal = true;
    },
    handleCloseTaskCreateModal(task) {
      this.isVisibleTaskCreateModal = false;
    },
    // asyncをつけることで、handleCreateTask関数は非同期であることを明示している。
    async handleCreateTask(task) {
      // try 例外処理。API通信で何な問題があった時画面にエラー出せるようにする。
      try {
        // awaitはpromiseを返す関数につけることによって、その処理が完了するのを待つ。
        await this.createTask(task)
        this.handleCloseTaskCreateModal()
      } catch (error) {
        console.log(error)
      }
    }
  }
}
</script>



Vuexの大まかな流れ

タスクが表示される流れを見ていきます。

タスク一覧画面を表示するボタンを押した時、Componentsのmethodsでdispatch()をする。

  methods: {
    ...mapActions([
      'getTasks',
      // getTasks() {
      //  this.$store.dispatch("getTasks")
      // }
    ]),

するとActionsに行ってサーバーと非同期的処理を行う。

actions: {
        getTasks({ commit }) {
          axios.get('tasks')
            .then(res => {
              commit('setTasks', res.data)
            })
            .catch(err => console.log(err.response));
        },

ここでcommitをすることによってMutationsでstateの情報を更新できるようになる。

tasksデータをstateに保存する関数を定義する。stateの情報を更新

mutations: {
    setTasks(state, tasks) {
      state.tasks = tasks
    },

stateが更新される

  state: {
    tasks: []
  },

stateからそのままComponentsに渡すのもありですが、今回はGettersを使いました。 stateの情報が書き変わるとGettersに任せてそのままComponentsに渡ことができます。

getters: {
    tasks: state => state.tasks
  },
computed: {
    ...mapGetters([ 'tasks' ])
  },

モーダルの表示 Vue.js

親子間で分ける必要性

タスク詳細モーダルを作成する上で、モーダル画面を子コンポーネントにタスク詳細を親コンポーネントに分けます。何故わざわざ分ける必要性があるのでしょうか?

一つのコンポーネントでデータはあるけど、細部のデザインとかでコンポーネントを分けて、その中にたくさんのコンポーネントを配置している時にそのコンポーネントにいろんなデータを渡したいというシーンがたくさんあるためです。



実装

モーダルの表示・非表示のやり方

v-ifに指定されたデータがtrueなら対象のHTMLタグは出力され、falseなら出力されません。この性質を活かしてモーダルの表示・非表示を制御していきます。

コンポーネント

index.vue

<template>
  <div class="d-flex flex-column min-vh-100">
     <div class="d-flex">
      <div class="col-4 bg-light rounded shadow m-3 p-3">
        <div class="h4">TODO</div>

        <!-- ① -->

          <div
          v-for="task in tasks"
          :key="task.id"
          :id="'task-' + task.id"
          class="bg-white border shadow-sm rounded my-2 p-4"
          @click="handleShowTaskDetailModal(task)"
        >
          <span>{{ task.title }}</span>
        </div>
      </div>

    <div class="text-center">
      <router-link :to="{ name: 'TopIndex' }" class="btn btn-dark mt-5">戻る</router-link>
    </div>

    <!-- ③ -->

    <transition name="fade"> <!-- ⑨ -->
      <TaskDetailModal v-if="isVisibleTaskDetailModal" :task="taskDetail" @close-modal="handleCloseTaskDetailModal" />
    </transition>
    </div>
  </div>
</template>

<script>
import TaskDetailModal from './components/TaskDetailModal.vue' 

export default {
  name: "TaskIndex",
  components: {
    TaskDetailModal
  },
  data() {
    return {
      tasks: [],
      taskDetail: {},
      isVisibleTaskDetailModal: false
    }
  },
  created() {
    this.fetchTasks();
  },
  methods: {
    fetchTasks() {
      this.$axios.get("tasks") //  baseURLを設定しているのでurlを省略できる
      .then(res => this.tasks = res.data) //  取得したtaskのデータをtasksプロパティに代入
      .catch(err => console.log(err.status));
    },

    // ②

    handleShowTaskDetailModal(task) {
      this.isVisibleTaskDetailModal = true;
      this.taskDetail = task;
    },

    // ⑩

    handleCloseTaskDetailModal() {
      this.isVisibleTaskDetailModal = false; //非表示にするためにfalseにする
      this.taskDetail = {};
    }
  }
}
</script>


コンポーネント

TaskDetailModal.vue

<template>
  <div :id="'task-detail-modal-' + task.id">
    <div class="modal" @click.self="handleCloseModal">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">

            <!-- ⑤ -->

            <h5 class="modal-title">{{ task.title }}</h5>
            <button type="button" class="close" @click="handleCloseModal">
              <span>&times;</span>
            </button>
          </div>
          <div class="modal-body" v-if="task.description">
            <p>{{ task.description }}</p>
          </div>
          <div class="modal-footer"> <!-- ⑥ -->
            <button type="button" class="btn btn-secondary" @click="handleCloseModal">閉じる</button>
          </div>
        </div>
      </div>
    </div>
    <div class="modal-backdrop show"></div>
  </div>
</template>

<script>
export default {
  name: "TaskDetailModal",

  // ④

  props: {
    task: {
      title: {
        type: String,
        required: true
      },
      description: {
        type: String,
        required: true
      }
    }
  },
  methods: {
    handleCloseModal() { // ⑦
      this.$emit('close-modal') // ⑧
    }
  }
}
</script>

<style scoped>
.modal {
  display: block;
}
</style>


番号解説

①各タスク名をクリックすると"handleOpenTaskDetailModal(task)"発火。v-for="task in tasks"でループしてtaskを一覧表示させています。 @click="handleOpenTaskDetailModal(task)" でそれぞれのtaskを引数として指定し、メソッドを発火させています。


②各タスククリックで発火される。handleShowTaskDetailModal(task)が実行されるとisVisibleTaskDetaiModalというモーダルが表示されているかどうかを管理する変数をtrueに変更します。TaskDetaiilModal.vueにデータを受け渡しするための変数taskDetailにtaskを代入しています。(これは子コンポーネントで使用します。)


③親から子へ渡すデータの送り口を作る。 親コンポーネントから子コンポーネントにデータを渡すには、親コンポーネントに:task="taskDetail"と属性名で定義できる。 (:taskはv-bind:taskの省略系)v-bindして値を指定することで子コンポーネントへ親コンポーネントで定義されたデータを受け渡すことができます。

<template>
  <TaskDetailModal :task="taskDetail"  />
</template>

<script>
 methods: {
    handleOpenTaskDetailModal(task) { 
      this.isVisibleTaskDetailModal = true; 
      this.taskDetail = task; // ループして引数で受けとったtaskを`taskDetail`に入れる
    },
</script>

これで子コンポーネントへ送る準備ができました。


④親からデータ受け取り。 親コンポーネントから子コンポーネントにデータを渡す時に使うのが、propsです。※このpropsは配列です。 propsは子コンポーネントに書きます。 ③で親コンポーネントから子コンポーネントに:taskを送りました。

  <TaskDetailModal :task="taskDetail"  />

コンポーネントでpropsを使って、taskの設定をしていきます。 これで、Modal.vueではvalを使うことで親からのデータを表示できます。

props: {
    task: {
      title: {
        type: String,
        required: true
      },
      description: {
        type: String,
        required: true
      }
    }
  },

taskのtitleとdescriptionのプロパティを設定したい時がある。そのような場合、プロパティをオブジェクトとして列挙することができます。このオブジェクトのキーと値には、それぞれプロパティ名と型を設定できます。


⑤親から受け取ったデータtask, descriptionを表示


⑥閉じるボタンをクリックすると、"handleCloseModal"メソッドが発火する


⑦閉じるボタンクリックにより発火する


⑧$emitで'close-modal'を親側に渡す。(親にデータの送り口を作る)


⑨v-on(@に省略可)で子から$emitで渡された'close-modal'を受け取り、メソッド発火。$emitとは、データを親コンポーネントに受け渡すもの というよりかは、子コンポーネントの好きなタイミングで親コンポーネントのメソッドを発火できる。クリックされ、@click=”$emit(‘close-modal’)”で親に「close-modalっていうイベントが起きたよ!!」と伝えます。 親側ではそれを@closeで受け取って@close="close-model"とすることでメソッドを呼び出す。


⑩子から$emitで渡された'close-modal'により発火するメソッド

shimablogs.com

Vue.js axios

axiosのインストール

今回、yarnでインストールしました。

yarn add axios



axiosを使う前の準備

まずは色々した準備をしていきます。 railsで実装していることから、CSRF対策をしていきます。 app/controller/application_contoroller

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end


CSRFとは

フルスタックフレームワークであるRailsにはCSRFクロスサイトリクエストフォージェリ)という脆弱性に対する機能が標準で用意されています。これはリクエスト時に正統なトークンも一緒に送らないとサーバ側で当該リクエストを不正なものとみなし弾く、という機能になります。

旧来のビューファイル込みのアプリケーションでは非常に有効な機能ではありましたが、昨今の「バックエンド」「フロントエンド」が明確に分離しているアプリケーションにおいてはこの機能が不要になります

router.rb

Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api do
    resources :tasks
  end

  get '*path', to: 'home#index'
end

ここ順番に注意してくだい!

タスク追加コマンドを打つ

curl -X POST -H "Content-Type: application/json" -d '{"title":"Rubyのサンプルコードを書く"}' localhost:3000/api/tasks

-XでPOSTメソッドを指定しています。

-dで送信するパラメータの値を指定しています。

-H 'Content-Type: application/json'でJSONデータを送信することを宣言します。 最後に送信先のURLを指定します。



axiosを使って、APIのgetメソッドを取得

javascript/packs/hello_vue.js

import axios from '../plugins/axios'

Vue.prototype.$axios = axios

axiosをimportします。 Vue.prototype.$axios = axiosを定義することで、全てのcomponentでaxiosが使えるようになります。


app/javascript/plugins/axios.js

import axios from 'axios'

const axiosInstance = axios.create({
  baseURL: 'api'
})

export default axiosInstance


javascript/pages/task/index.vue

<script>
export default {
  name: "TaskIndex",
  data() {
    return {
      tasks: []
    }
  },
  created() {
    this.fetchTasks();
  },
  methods: {
    fetchTasks() {
      this.$axios.get("tasks")
      .then(res => this.tasks = res.data )
      .catch(err => console.log(err.status));
    }
  }
}
</script>

createdで全てのデータを取得する。

axios.getは、2つの引数を書けます。 第一引数:取得したいサーバーのURL 第二引数:そのリクエストの設定

.then(res)ってなんだろう? console.log(res)で見てみると Image from Gyazo

今回取得したいデータが格納されているdataやその他にも色々組み込まれているオブジェクトということがわかりました。 catch(err => console.log(err.status));で、エラーがある場合は、statusのエラー内容を取得します。

Vue Router

Vue Routerについて

Vue Routerを使うことでアクセスしたURLと呼び出すVueコンポーネントを紐付けることができます。

例えば、 / にアクセスしたら、 Top.vue を呼び出す。 /tasks にアクセスしたら、 Tasks.vue を呼び出すことが実現できます。

特に、SPA(シングルページアプリケーション)は単一のHTMLで動作するアプリケーションのため、画面の「戻る」や「進む」といった履歴を管理できないが、Vue Routerを使うことで管理できるのもメリットのひとつです。


実装

タスク管理アプリで、トップ画面⇔タスク一覧画面と遷移できるように設定していきます。

root /にアクセスすると、home#index home_controllerのindexアクションへ飛びます。 app/controllers/homes_controller.rb

class HomesController < ApplicationController
  def index; end
end

app/views/homes/index.html.erb

<%= javascript_pack_tag 'hello_vue' %>



Vue-Routerの設定

javascript/routerディレクトリを新たに作成し、その配下に、index.jsファイルを作成します。これがVueRouterの仕組み全体のファイルとなり、Railsのroutes.rbと同じような役割を担うことになる。

javascript/router/index.js

import Vue from 'vue';
import Router from "vue-router";

// ルート用のコンポーネントを読み込む
import TopIndex from "../pages/top/index.vue";
import TaskIndex from "../pages/task/index.vue";

// プラグインとして登録。これでRouterを使用できるVue.use(Router)

// Routerインスタンスを生成
const router = new Router({
  mode: 'history', // URLにハッシュをつけない
  routes: [
    {
      path: '/',
      component: TopIndex,
      name: "TopIndex", // 名前付きルート
    },
    {
      path: '/tasks',
      component: TaskIndex,
      name: "TaskIndex",
     },
  ],
})

// 作ったRouterインスタンスをエクスポート
export default router

VueとVueRouterをindex.jsにインポートする。

/にアクセスしたら、/pages/top/index.vueを読み込む、 /tasks にアクセスしたら、/pages/task/index.vueを読み込む、というように設定していきます。

上記のように記述していくと、<router-link>に名前で指定することができるようになります。

const router = new VueRouterで新しくVueRouterのインスタンスを作成して、HTML5 Historyモードを使用してルーティングを記述していきます。 ルーティングを記述していく際に名前付きルートを使って記述していきます。

javascript/pages/top/index.vue

<template>
  <router-link :to="{ name: 'TaskIndex' }" class="btn btn-dark mt-5">タスク一覧へ</router-link>
</template>

javascript/pages/top/index.vue

<template>
  <router-link :to="{ name: 'TopIndex' }" class="btn btn-dark mt-5">戻る</router-link>
</template>



ルーターの初期化

import Vue from 'vue'
import App from '../app.vue' //App.vueを読み込む

import router from '../router'
import 'bootstrap/dist/css/bootstrap.css'

Vue.config.productionTip = false

// Vueアプリケーション起動
document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    router, // routerをVueインスタンスへ渡している
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)
})

My SQL rootユーザのパスワードを再設定

パスワードなしでログイン

mysql -u root

使うデータベースを標準のmysqlのものに変更します

mysql> use mysql
Database changed

パスワードを再設定する

mysql> UPDATE user SET authentication_string=password('新規パスワード') WHERE user='root';
Query OK, 1 row affected, 1 warning (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 1

再設定したパスワードをMySQLに反映させます

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

My SQLからログアウト

mysql> quit
Bye

以下のコマンドを入力し、リセットしたパスワードを入力してログインできれば完了

$ mysql -u root -p
Enter password: