Reactのチュートリアルの三目並べで少しRails使う #1

何度もくどいですが、[前回](https://qiita.com/GodPhwng/items/ac8bc9c6275320bb62d4)までのReactチュートリアルの三目並べをせっかくなので勉強も兼ねてRuby Railsで作ってみようと思います。

RubyおよびRailsはProgateの無料レッスンをこなした程度です。

**無理やりerbでやらせて、ボタンを押下するたびにformをsubmitしたら、超もっさりになりました・・・。**

 - そもそも小規模なので、クライアントで完結させた方がよい
 - 使うにしてもクライアントはReactなり他のフレームワークやライブラリに任せればよい
 - やり取りはajaxにして、Rails側はデータの出し入れだけやればよい

にすればよかたかも・・・。

#開発環境
**Rails環境：[以前の記事](https://qiita.com/GodPhwng/items/7ab78de4342649b5725e)で構築した環境を使用する**
**エディタ：VSCode**

#M(Model)V(View)C(Controller)を用意する
##Controller
ターミナルで以下を実行して、コントローラーを追加します。

```
rails generate controller tictactoes
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/7129f1bf-dd4c-2d08-3267-60dd1c2a35c4.png)

ファイルの内容はデフォルトのindexアクションがあるのみ

```ruby:tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
    end
end
```


##View
コントローラー作成の際に以下に`tictactoes`ディレクトリが作成されているので、そこに`index.html.erb`を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/8eb75058-a3b4-2839-1a68-06593d6478ee.png)

htmlの構成は[前回](https://qiita.com/GodPhwng/items/ac8bc9c6275320bb62d4)まで使用していた`index.html`をコピーします。
Railsだと`DOCTYPE`や`html`、`body`等の共通部分は自動的に作成してくれる？ようなので、必要最低限をコピーしました。

```erb:index.html.erb
<head>
  <!--<link rel="stylesheet" href="css/index.css" />-->
  <!--<script src="js/index.js"></script>-->
</head>

<body id="tictactoes-body">
  <div id="root">
    <div class="game">
      <div class="gmae-board">
        <div>
          <div class="board-row">
            <button class="square"></button><button class="square"></button
            ><button class="square"></button>
          </div>
          <div class="board-row">
            <button class="square"></button><button class="square"></button
            ><button class="square"></button>
          </div>
          <div class="board-row">
            <button class="square"></button><button class="square"></button
            ><button class="square"></button>
          </div>
        </div>
      </div>
      <div class="game-info">
        <div>次の手番: X</div>
        <div>
          <li><button>Go to game start</button></li>
          <!-- <li><button>Go to move #1</button></li> -->
        </div>
      </div>
    </div>
  </div>
</body>
```
`body`に`id`をつけているのは、他画面の`body`のcss設定が効いてしまって背景が青がかった緑色になってしまったので、そのスタイルを上書きするためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/c7d23c9a-ffdf-9c9a-c6de-64f962ca4267.png)
**↑**[RailsをProgate無料レッスンでやった時](https://qiita.com/GodPhwng/items/7ab78de4342649b5725e)の`home.scss`の`body`のスタイルが効いてしまう。

スタイルが全体に効いてしまう理由は以下を参考にさせていただきました。
[RailsがJavaScriptやスタイルシートを読み込む仕組み](https://www.ryotaku.com/entry/2019/02/19/215229)

CSSは`assets/stylesheees`配下に自動的に作成されるのでそこに[前回](https://qiita.com/GodPhwng/items/ac8bc9c6275320bb62d4)の`index.css`をコピーしていきます。

```scss:tictactoes.scss
#tictactoes-body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
  background-color: white;

  ol,
  ul {
    padding-left: 30px;
  }

  .board-row:after {
    clear: both;
    content: "";
    display: table;
  }

  .status {
    margin-bottom: 10px;
  }

  .square {
    background: #fff;
    border: 1px solid #999;
    float: left;
    font-size: 24px;
    font-weight: bold;
    line-height: 34px;
    height: 34px;
    margin-right: -1px;
    margin-top: -1px;
    padding: 0;
    text-align: center;
    width: 34px;
  }

  .square:focus {
    outline: none;
  }

  .kbd-navigation .square:focus {
    background: #ddd;
  }

  .game {
    display: flex;
    flex-direction: row;
  }

  .game-info {
    margin-left: 20px;
  }
}
```

##ルーティング
`routes.rb`にGETリクエストのルーティングを追加します。

```ruby
get 'tictactoes' => 'tictactoes#index'
```

ここでブラウザで確認してみたいと思います。

ターミナルから

```
rails server
```
を実行した後、`http://localhost:3000/tictactoes`にアクセス。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/afdf2fad-6935-f8d7-5229-be9547da50ac.png)

##Model
マス目のクリック状態はDBに持つことにします。
以下のテーブル構成にしました。

テーブル名：squares
カラム：

| カラム名| 型 | 主キー |   説明・備考     |
|:--|:--|:--:|:--|
| stepNumber|integer |O|手順番号。主キー|
| squareNumber|integer |O|マス目番号。主キー|
| content|string||マス目の中の値(O/X)|

migrationファイルを作っていきます。
以下をターミナルで実行(以下はModelを作って、ついでにmigrationファイルが作られてます。)

```
rails generate model Square stepNumber:integer:unique squareNumber:integer:unique content:string
```
以下が作成されます。

```ruby:models/square.rb
class Square < ApplicationRecord
end
```

```ruby:XXXXX_create_squares.rb
class CreateSquares < ActiveRecord::Migration[6.0]
  def change
    create_table :squares do |t|
      t.integer :stepNumber 
      t.integer :squareNumber
      t.string :content

      t.timestamps
    end
  end
end
```
あれ？できたmigrationファイルに主キーの指定がない・・・
なんでや・・・

理由はわかりませんが、手動で以下を追加を追加すればよいと思います。
※null不可も追加しました。
主キーに指定しただけでは勝手にnull不可とはならないようです。
後で他のSQLクライアントでスキーマ情報を見てみるとわかりますが、`id`というカラムが主キー、null不可で自動的に作成されています。
そのカラムがあるからでしょうか・・・。

```ruby
add_index :squares, [:stepNumber, :squareNumber], :unique => true
change_column :squares, :stepNumber, :integer, null: false
change_column :squares, :squareNumber, :integer, null: false
```

私は一度テーブルを作ってしまって後から追加しました。

ファイルが用意できたら、マイグレーションファイルを実行

```
rails db:migrate
```

フリーのSQLクライアント
[A5:SQL Mk-2](https://a5m2.mmatsubara.com/)
を使用してテーブルを確認してみます。

インストール後、起動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/9e008b11-63b8-89d8-f32c-44bce4353c81.png)
「データベース」を右クリック　→　「データベースの追加と削除」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/1ac3d28f-4857-7393-091e-67affa4c6441.png)
左下の「追加」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/8387f0c5-953e-8761-abf1-088c2122ef8b.png)
「SQLite(sqlite3.dll経由)」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/36cd0105-0677-6218-0905-fb13c9eecfe3.png)
`db/development.sqlite3`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/8ec886e7-da8a-2c1c-68a8-491c1f2a9873.png)
追加すると、左のツリーに合わられるので、展開してテーブル情報を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/48730c3d-be42-d04b-aa35-f61f3b02260a.png)
ログイン情報を求められたら、空欄のままOKで良いです。

squaresのスキーマの確認結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/6ff72375-f76b-9cdd-e303-0514ea107733.png)

データタブを開き、マス目に未入力の状態のレコードを作成します。
Excel等で作って、コピー&ペーストすればデータが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/30e98ef6-2cc2-662c-6607-6fe52af46ea1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/ae725e5a-ef3d-792a-b8e3-8771f24fc864.png)

#マス目に数字を表示する
squareテーブルの各レコード.contentに数値を入れておいて、その値を表示させたいと思います。

squareテーブル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/c35b9fdb-2444-e5d6-635e-981dbb80b968.png)

全件取得します。

```ruby:tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
        @Squares = Square.all
    end
end
```
erbの方ではマス目が３つごとに`<div class="board-row">`に囲われる必要があるので、if文で制御してます。

```erb:index.html.erb
<head>
  <!--<link rel="stylesheet" href="css/index.css" />-->
  <!--<script src="js/index.js"></script>-->
</head>

<body id="tictactoes-body">
  <div id="root">
    <div class="game">
      <div class="gmae-board">
        <div>
          <% @Squares.each.with_index do |sq, i| %>
            <% if i % 3 == 0%>
              <div class="board-row"></div>
            <% end %>
            <button class="square">
              <%= sq.content %>
            </button>  
          <% end %>
        </div>
      </div>
      <div class="game-info">
        <div>次の手番: X</div>
        <div>
          <li><button>Go to game start</button></li>
          <!-- <li><button>Go to move #1</button></li> -->
        </div>
      </div>
    </div>
  </div>
</body>
```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/b71c83c1-38bd-2b54-d601-92895001788f.png)

#X/Oを入力できるようにする

全然Railsわからなくてめちゃ苦戦しました。
突っ込みどころ満載かもですが

```javascript:tictactoes.js
(function() {
  window.addEventListener("DOMContentLoaded", () => {
    // 全square
    document.querySelectorAll(".square").forEach((element, index) => {
      // クリックイベント
      element.addEventListener("click", squareClick.bind(element, index));
    });

    let isClick = false;
    function squareClick(index) {
      // 2度押し防止、既に値が埋まっているか
      if (isClick || this.value) {
        return;
      } else {
        isClick = true;
      }

      document.getElementById("clickNo").value = index;
      document.forms[0].submit();
    }
  });
})();
```
formのsubmitなのでもっさりしており、クリック連打で重複してリクエストしていたので、防止しました。
また、学習の為、ほとんどサーバー側でやってみよう！と思っていたのですが私には制御が難しくて、「既に埋まっていたら」という判定はクライアントでやっています。


自作jsを何処に置けばよいかググっていたところ、`assets/javascripts`らしいのだが、これはRails5までであり、
Rails6では、`assets/javascripts`フォルダが無くなっており、jsを何処に置けばいいのかわからない。

→　`app/javascript/packs`に配置して、読み込ませたいhtmlのヘッダで以下のように指定すると読み込めた。

```erb
<%= javascript_pack_tag 'tictactoes', 'data-turbolinks-track': 'reload' %>
```

でもこれだと、複数のjsを読み込むことになり、１つに圧縮してリクエストを減らしていた恩恵が受けられなくなる・・・。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/2762d1ae-af27-509c-6cce-e77c82eb9385.png)

ﾄﾞｳｼﾖｳ

erbの方はform_withというものを使ってみます。

```erb:index.html.erb
<head>
  <!--<link rel="stylesheet" href="css/index.css" />-->
  <!--<script src="js/index.js"></script>-->
  <%= javascript_pack_tag 'tictactoes', 'data-turbolinks-track': 'reload' %>
</head>

<body id="tictactoes-body">
  <%= form_with(url: "/tictactoes/sqClick", method: "post") do |f| %>
    <div id="root">
      <div class="game">
        <div class="gmae-board">
          <div>
            <% @Squares.each.with_index do |sq, i| %>
              <% if i % 3 == 0%>
                <div class="board-row"></div>
              <% end %>
              <%= f.text_field '', :name => "item[]", :class => "square", :readonly => true , :value => sq.content%>              
              
            <% end %>
          </div>
        </div>
        <div class="game-info">
          <div><%= @nextStepMessage %></div>
          <div>
            <li><button>Go to game start</button></li>
            <!-- <li><button>Go to move #1</button></li> -->
          </div>
        </div>
      </div>
    </div>
    <%= f.hidden_field :clickNo, :value => @clickNo %>
    <%= f.hidden_field :xIsNext, :value => @xIsNext %>
    <%= f.hidden_field :stepNumber, :value => @stepNumber %>
  <% end %>
</body>
```
うーん。独特すぎて謎汗

サーバー側

```ruby:tictactoes_controller.rb
class TictactoesController < ApplicationController
    #初期画面表示
    def index
        
        # 手順1以降を削除して初期化
        Square.where.not(stepNumber: 0).delete_all

        #手順0を全件取得
        @Squares = Square.all

        #初期はX
        @xIsNext = "true"

        #手順の開始は0から
        @stepNumber = 0

        #次の手番の文字列
        @nextStepMessage = "次の手番: X"
    end
    
    #マス目クリック
    def sqClick

        #現在の手順インクリメント
        stepNumber = params[:stepNumber].to_i
        @stepNumber = stepNumber.succ

        #マス目の配列
        items = params[:item]                
        sq = Square.new

        #既に勝敗が着いた      
        if sq.calculateWinner(items)
            return
        else 

            #XとOを設定
            items[params[:clickNo].to_i] = params[:xIsNext] == "true" ? "X": "O"            
            isWinner = sq.calculateWinner(items)

            #新しい手順をDBに保存          
            sq.saveSquare(@stepNumber, items)

            #フラグの更新
            @xIsNext = params[:xIsNext] == "true" ? "false" : "true"

            #次の手番の文字列更新    
            if isWinner
                @nextStepMessage = "勝者：" + (params[:xIsNext] == "true" ? "X": "O")    
            else
                @nextStepMessage = params[:xIsNext] == "true" ? "次の手番: O": "次の手番: X"
            end
                
            #最新の手順を取り直す
            @Squares = Square.where(stepNumber: @stepNumber)
        end

        #indexのhtmlを使いまわす
        render action: :index
    end
end
```
自分でも何をやっているのやら・・・。

@変数　でview側に値を埋め込めます。
`render action: :index`で`index.html.erb`を使いまわしています。
判定処理`calculateWinner`やレコード追加処理はモデル`Square`でやっています。

```ruby:Square.rb
class Square < ApplicationRecord

    Lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
      ]

    def saveSquare(stepNumber, items)                
        #新しい手順のマス目保存処理
        for i in 1..9 do
            Square.create(stepNumber: stepNumber, squareNumber: i, content: items[i - 1])
        end     
    end

    #勝敗判定関数(公式チュートリアルから拝借)
    def calculateWinner(items)

        for l in Lines do
            a, b, c = l

            if items[a] != "" && items[a] == items[b] && items[a] == items[c]
                return true
            end
        end

        return false
    end
end
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/c1e7c44f-8a7b-476e-bf5a-c9bd1e33e4aa.png)

テーブルデータ

|id|stepNumber|squareNumber|content|created_at|updated_at|
|---|---|---|---|---|---|
|1|0|1||2020/03/29|1899/12/30 1:50:48|
|2|0|2||2020/03/29|1899/12/30 1:51:11|
|3|0|3||2020/03/29|1899/12/30 1:51:11|
|4|0|4||2020/03/29|1899/12/30 1:50:48|
|5|0|5||2020/03/29|1899/12/30 1:51:11|
|6|0|6||2020/03/29|1899/12/30 1:51:11|
|7|0|7||2020/03/29|1899/12/30 1:50:48|
|8|0|8||2020/03/29|1899/12/30 1:51:11|
|9|0|9||2020/03/29|1899/12/30 1:51:11|
|10|1|1||2020/03/31 14:50:05|2020/03/31 14:50:05|
|11|1|2||2020/03/31 14:50:05|2020/03/31 14:50:05|
|12|1|3|X|2020/03/31 14:50:05|2020/03/31 14:50:05|
|13|1|4||2020/03/31 14:50:05|2020/03/31 14:50:05|
|14|1|5||2020/03/31 14:50:05|2020/03/31 14:50:05|
|15|1|6||2020/03/31 14:50:05|2020/03/31 14:50:05|
|16|1|7||2020/03/31 14:50:05|2020/03/31 14:50:05|
|17|1|8||2020/03/31 14:50:05|2020/03/31 14:50:05|
|18|1|9||2020/03/31 14:50:05|2020/03/31 14:50:05|
|19|2|1||2020/03/31 14:50:06|2020/03/31 14:50:06|
|20|2|2||2020/03/31 14:50:06|2020/03/31 14:50:06|
|21|2|3|X|2020/03/31 14:50:06|2020/03/31 14:50:06|
|22|2|4||2020/03/31 14:50:06|2020/03/31 14:50:06|
|23|2|5||2020/03/31 14:50:06|2020/03/31 14:50:06|
|24|2|6|O|2020/03/31 14:50:06|2020/03/31 14:50:06|
|25|2|7||2020/03/31 14:50:06|2020/03/31 14:50:06|
|26|2|8||2020/03/31 14:50:06|2020/03/31 14:50:06|
|27|2|9||2020/03/31 14:50:06|2020/03/31 14:50:06|
|28|3|1||2020/03/31 14:50:07|2020/03/31 14:50:07|
|29|3|2||2020/03/31 14:50:07|2020/03/31 14:50:07|
|30|3|3|X|2020/03/31 14:50:07|2020/03/31 14:50:07|
|31|3|4||2020/03/31 14:50:07|2020/03/31 14:50:07|
|32|3|5||2020/03/31 14:50:07|2020/03/31 14:50:07|
|33|3|6|O|2020/03/31 14:50:07|2020/03/31 14:50:07|
|34|3|7||2020/03/31 14:50:07|2020/03/31 14:50:07|
|35|3|8||2020/03/31 14:50:07|2020/03/31 14:50:07|
|36|3|9|X|2020/03/31 14:50:07|2020/03/31 14:50:07|
|37|4|1||2020/03/31 14:50:10|2020/03/31 14:50:10|
|38|4|2||2020/03/31 14:50:10|2020/03/31 14:50:10|
|39|4|3|X|2020/03/31 14:50:10|2020/03/31 14:50:10|
|40|4|4||2020/03/31 14:50:10|2020/03/31 14:50:10|
|41|4|5||2020/03/31 14:50:10|2020/03/31 14:50:10|
|42|4|6|O|2020/03/31 14:50:10|2020/03/31 14:50:10|
|43|4|7||2020/03/31 14:50:10|2020/03/31 14:50:10|
|44|4|8|O|2020/03/31 14:50:10|2020/03/31 14:50:10|
|45|4|9|X|2020/03/31 14:50:10|2020/03/31 14:50:10|
|46|5|1||2020/03/31 14:50:11|2020/03/31 14:50:11|
|47|5|2||2020/03/31 14:50:11|2020/03/31 14:50:11|
|48|5|3|X|2020/03/31 14:50:11|2020/03/31 14:50:11|
|49|5|4||2020/03/31 14:50:11|2020/03/31 14:50:11|
|50|5|5|X|2020/03/31 14:50:11|2020/03/31 14:50:11|
|51|5|6|O|2020/03/31 14:50:11|2020/03/31 14:50:11|
|52|5|7||2020/03/31 14:50:11|2020/03/31 14:50:11|
|53|5|8|O|2020/03/31 14:50:11|2020/03/31 14:50:11|
|54|5|9|X|2020/03/31 14:50:11|2020/03/31 14:50:11|
|55|6|1||2020/03/31 14:50:12|2020/03/31 14:50:12|
|56|6|2||2020/03/31 14:50:12|2020/03/31 14:50:12|
|57|6|3|X|2020/03/31 14:50:12|2020/03/31 14:50:12|
|58|6|4||2020/03/31 14:50:12|2020/03/31 14:50:12|
|59|6|5|X|2020/03/31 14:50:12|2020/03/31 14:50:12|
|60|6|6|O|2020/03/31 14:50:12|2020/03/31 14:50:12|
|61|6|7|O|2020/03/31 14:50:12|2020/03/31 14:50:12|
|62|6|8|O|2020/03/31 14:50:12|2020/03/31 14:50:12|
|63|6|9|X|2020/03/31 14:50:13|2020/03/31 14:50:13|
|64|7|1|X|2020/03/31 14:50:14|2020/03/31 14:50:14|
|65|7|2||2020/03/31 14:50:14|2020/03/31 14:50:14|
|66|7|3|X|2020/03/31 14:50:14|2020/03/31 14:50:14|
|67|7|4||2020/03/31 14:50:14|2020/03/31 14:50:14|
|68|7|5|X|2020/03/31 14:50:14|2020/03/31 14:50:14|
|69|7|6|O|2020/03/31 14:50:14|2020/03/31 14:50:14|
|70|7|7|O|2020/03/31 14:50:14|2020/03/31 14:50:14|
|71|7|8|O|2020/03/31 14:50:14|2020/03/31 14:50:14|
|72|7|9|X|2020/03/31 14:50:14|2020/03/31 14:50:14|




#感想
Railsの書き方が独特すぎて超苦戦してます。~~今のところメリット感じない・・・~~

次回は履歴機能を持たせて完成に持っていこうと思います。

いつかRailsのチュートリアルもやってもっと基礎から理解しよう・・・。

→　[次回](https://qiita.com/GodPhwng/items/be2970dfce435bed23d6)