React+Railsでモダン（）なTODOアプリを作ってみた 〜備忘録も兼ねて〜

[Opt Technologies Advent Calendar 2019](https://qiita.com/advent-calendar/2019/opt-technologies)の21日目の記事です。
# はじめに
2019年4月に株式会社オプトに入社したsh1okohと申します。
なんやかんやありまして、今はReact, Redux, Railsなどを使ったプロダクトで、日々奮闘しております。
今回は、お仕事中に出会った技術スタックを使ってモダン()なTODOアプリを作ってみたので、各種ライブラリの紹介をしつつ、Todoアプリの紹介をしていきたいと思います。（備忘録的な目的もあります）

ちなみに、今回のTODOアプリのソースコードは下記リンクにありますので、
興味のある方はみてみてください。

https://github.com/sh1okoh/adventcalendar

# 対象読者
今回は、テーマ的にも内容自体は広く浅くになっております。
各種ライブラリを使ったSPAの全体像を把握したい方などを対象としており、各ライブラリを深く学びたいといった方は対象外となっておりますので、よろしくお願いします。

# 今回使用した技術
## フロントエンド
- JavaScript （業務ではTypeScriptを使っています）
- React
- ~~Redux~~
- axios
- eslint
- prettier

## バックエンド
- Ruby 2.5.7
- Ruby on Rails 5.2.4
- rubocop
- guard
- rspec
- rack-cors

## CI/CD
- Circle CI

# フロントエンド編
## React とは
> A JavaScript library for building user interfaces

https://reactjs.org/

Facebookの作ったJavascriptライブラリです。
MVC(Model View Controller)モデルでいうとVの部分を提供しています。

### Reactの考え方
> Component-Based
Build encapsulated components that manage their own state, then compose them to make complex UIs.

https://reactjs.org/


Reactでは、画面をComponentの親子関係を持つツリーとして構成します。 
各コンポーネントは、親から渡されたprops（プロパティ）、State（自分の状態）を元に、renderメソッドでDOM（Virtual DOM）を生成します。

## axios とは
> Promise based HTTP client for the browser and node.js

https://github.com/axios/axios

Http通信を簡単に行うことができるJavascriptライブラリです。
主な特徴としては、

- ChromeやFirefox, Safariなどのブラウザに対応
- リクエストとレスポンスデータを変換
- Promise APIをサポート

と言った特徴があります。

今回は、このライブラリを使ってAPIとの通信を行なっていきます。

## 実装

以下がコードになります。

```javascript
import React from 'react';
import axios from 'axios';

const Title = ({todoCount}) => {
  return (
    <div>
      {
          todoCount > 0 ? <h1>{todoCount}つのタスクがあります</h1> :
          <h1>タスクがありません</h1>
      }
    </div>
  );
}

const TodoForm = ({addTodo}) => {
  let input;
  return (
    <form onSubmit={(e) => {
        e.preventDefault();
        addTodo(input.value);
        input.value = '';
      }}>
      <input className="form-control col-md-12" ref={node => {
        input = node;
      }} />
      <br />
    </form>
  );
};

const Todo = ({todo, remove}) => {
  return (
    <li>
      <a href="#" className="list-group-item">{todo.contents}</a>
      <button onClick={() => {remove(todo.id)}}>削除</button>
    </li>
  );
}

const TodoList = ({todos, remove}) => {
  const todoNode = todos.map((todo) => {
    return (<Todo todo={todo} key={todo.id} remove={remove} />)
  });
  return (<div className="list-group" style={{marginTop:'30px'}}>{todoNode}</div>);
}

window.id = 0;
class TodoApp extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      data: []
    }
    this.apiUrl = 'http://localhost:3000/api/todos/'
  }

  componentDidMount(){
    axios.get(this.apiUrl)
      .then((res) => {
        this.setState({ data: res.data });
      });
  }

  addTodo(val){
    const todo = {contents: val}
    axios.post(this.apiUrl, todo)
       .then((res) => {
          this.state.data.push(res.data);
          this.setState({data: this.state.data});
       });
  }

  handleRemove(id){
    const remainder = this.state.data.filter((todo) => {
      if(todo.id !== id) return todo;
    });

    axios.delete(this.apiUrl+id)
      .then((res) => {
        this.setState({data: remainder});
      })
  }

  render(){

    return (
      <div>
        <Title todoCount={this.state.data.length}/>
        <TodoForm addTodo={this.addTodo.bind(this)}/>
        <TodoList
          todos={this.state.data}
          remove={this.handleRemove.bind(this)}
        />
      </div>
    );
  }
}

export default TodoApp
```
上記のコードを見てもらうと分かりやすいと思うのですが、`TodoApp`コンポーネントに`constructor`メソッド、`componentDidMount`メソッド, `render`メソッドを定義しています。`componentDidMount`メソッド, `render`メソッドは、Reactのライフサイクルメソッドの一種です。`componentDidMount`メソッドは、出力が DOM にレンダーされた後に実行されます。ここではライフサイクルの話は割愛しますので、気になる方は下記リンクを参考にしてみてください。

- https://ja.reactjs.org/docs/state-and-lifecycle.html
- https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9

で、各関数コンポーネントに、propsを渡し、その値に応じた描画を行なうという流れになっております。

APIの疎通に関しては、

```javascript
axios.get
```

や

```javascript
axios.post
```
で行なって、APIからのレスポンスデータを取得したりしています。

で、実際に見てみると、初期の出力結果が、こうなります。（めっちゃ殺風景・・・。）
<img width="1037" alt="スクリーンショット 2019-12-21 22.14.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100286/f2460964-26a5-73d4-ecf0-157e95b95d06.png">

で入力欄に値を入力してEnterを押すと、タスクが追加できて、削除までできます。
<img width="1037" alt="スクリーンショット 2019-12-21 22.17.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100286/1baf1a7c-a1ff-5207-75ab-e0e3984360d7.png">

本当は最低限CRUDの実装はしたかったのですが、諸々の事情により、updateの機能は後日追記いたします。

# バックエンド編
## Ruby on Rails とは
> Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern.

https://github.com/rails/rails

Model-view-controller パターンを採用したフレームワークです。
特にActiceRecordは個人的には強力な機能だと思っています。
 
今回はView側はReactで実装していますので、RailsのAPIモードを使って実装しました。

APIモードは、下記のようにコマンドライン引数に `--api`とつけるだけで、できてしまいます（さすがRails！）

```
rails new my_api --api
```
APIモード
https://guides.rubyonrails.org/api_app.html

## Active Record とは 
> Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.

https://guides.rubyonrails.org/active_record_basics.html

MVCモデル言うところの、Mの部分に相当するものです。
ORM（O/Rマッピング）システムに記述されている「Active Recordパターン」を実装したもので、このパターンと同じ名前が付けられています。

ORM(O/Rマッピング)とは、アプリケーションが持つオブジェクトを、RDBMSのテーブルに接続することです。
ORMを用いると、SQL文を書かずに、簡潔なコードだけで、テーブルのレコードのデータを取得できたり、更新できたりしてとても便利です。

例えば、ターミナル上で、

```
rails console
```
を叩き

```
Todo.all
```

などとすると、下記のような出力結果を得ることができます。

<img width="734" alt="スクリーンショット 2019-12-21 23.16.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100286/f5281acb-0001-f56f-e797-129cb1806905.png">

またワンライナーでも書くことができるので、
SQL文を書くより遥かに楽だと言うことが分かると思います。

他にも

- モデル同士のアソシエーションを表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーションを行う

などの特徴を持っています。

## RuboCop とは
> RuboCop is a Ruby static code analyzer and code formatter. 

https://github.com/rubocop-hq/rubocop

rubyのコードアナライザーであり、フォーマッターです。
例えば、チーム開発をするときにコード規約を定めると思うのですが、RuboCopを用いると捗ります。
また、警告を出すのみに止まらず、いくつかの問題を自動で直してくれることもしてくれます。
例えば、ターミナル上で下記のコマンドを実行すると、一括でフォーマットをかけてくれたりしますので、とても便利です。

```
bundle exec rubocop -a
```

参考
https://rubocop.readthedocs.io/en/stable/



## 実装
実装はとても簡単で、ターミナル上で、下記のコマンドをバーン！すると、
ルーティングやコントローラー、モデルからテーブル定義までの雛形を作ってくれます。

```
bundle exec rails generate scaffold Todo contents:string
```

実際のコード

`controllers/todos_controller.rb`

```ruby
class TodosController < ApplicationController
  before_action :set_todo, only: [:show, :edit, :update, :destroy]

  def index
    @todos = Todo.all
    render json: @todos
  end

  def show
    render json: @todo
  end

  def new
    @todo = Todo.new
  end

  def edit
  end

  def create
    @todo = Todo.new(create_params)
    if @todo.save
      render json: @todo
    else
      render :new
    end
  end

  def update
    if @todo.update!(update_params)
      render json: @todo
    else
      render :edit
    end
  end

  def destroy
    @todo.destroy
    render json: @todos
  end

  private
    def set_todo
      @todo = Todo.find(params[:id])
    end

    def create_params
      params.require(:todo).permit(:contents)
    end

    def update_params
      params.require(:todo).permit(%i[id contents])
    end

    def destroy_params
      params.require(:todo).permit(:id)
    end
end

```

`config/routes.rb`

```ruby
Rails.application.routes.draw do
  scope :api, defaults: { format: :json } do
    resources :todos, only: %i[show index create update destroy]
  end
end

```

`models/todo.rb`

```ruby
class Todo < ApplicationRecord
  def as_json(options = {})
    super(options.reverse_merge(except: %i[created_at updated_at]))
  end
end

```

`db/schema.rb`

```ruby
ActiveRecord::Schema.define(version: 2019_12_15_104719) do

  create_table "todos", force: :cascade do |t|
    t.string "contents"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end
```


今回のAPIの設計方針として、REST APIを採用しました。
REST APIについては、下記を参照してみてください。
https://restfulapi.net/


# CI編
後日書きます

# まとめ
半年間、自分が使ってきた技術を用いて、アプリーケーションを開発するのは良い振り返りにもなるし、一年の節目にも良さそうと思いました。今後も、何か学ぶ機会があれば、やってみるといいかもと思いました。
実はまだまだ書きたいこともあるので、後日追記するか、別の記事に載せることにします。

# その他の参考資料
https://qiita.com/matzkoh/items/90baab22ad489b78384b
https://restful-api-guidelines-ja.netlify.com/
https://scotch.io/tutorials/create-a-simple-to-do-app-with-react