0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-31

何度もくどいですが、前回までのReactチュートリアルの三目並べをせっかくなので勉強も兼ねてRuby Railsで作ってみようと思います。

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

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

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

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

#開発環境
Rails環境:以前の記事で構築した環境を使用する
エディタ:VSCode

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

rails generate controller tictactoes

image.png

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

tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
    end
end

##View
コントローラー作成の際に以下にtictactoesディレクトリが作成されているので、そこにindex.html.erbを作成します。
image.png

htmlの構成は前回まで使用していたindex.htmlをコピーします。
RailsだとDOCTYPEhtmlbody等の共通部分は自動的に作成してくれる?ようなので、必要最低限をコピーしました。

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>

bodyidをつけているのは、他画面のbodyのcss設定が効いてしまって背景が青がかった緑色になってしまったので、そのスタイルを上書きするためです。
image.png
RailsをProgate無料レッスンでやった時home.scssbodyのスタイルが効いてしまう。

スタイルが全体に効いてしまう理由は以下を参考にさせていただきました。
RailsがJavaScriptやスタイルシートを読み込む仕組み

CSSはassets/stylesheees配下に自動的に作成されるのでそこに前回index.cssをコピーしていきます。

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リクエストのルーティングを追加します。

get 'tictactoes' => 'tictactoes#index'

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

ターミナルから

rails server

を実行した後、http://localhost:3000/tictactoesにアクセス。
image.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

以下が作成されます。

models/square.rb
class Square < ApplicationRecord
end
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不可で自動的に作成されています。
そのカラムがあるからでしょうか・・・。

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
を使用してテーブルを確認してみます。

インストール後、起動
image.png
「データベース」を右クリック → 「データベースの追加と削除」
image.png
左下の「追加」
image.png
「SQLite(sqlite3.dll経由)」を選択
image.png
db/development.sqlite3を選択します。
image.png
追加すると、左のツリーに合わられるので、展開してテーブル情報を確認します。
image.png
ログイン情報を求められたら、空欄のままOKで良いです。

squaresのスキーマの確認結果
image.png

データタブを開き、マス目に未入力の状態のレコードを作成します。
Excel等で作って、コピー&ペーストすればデータが作成されます。
image.png

image.png

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

squareテーブル
image.png

全件取得します。

tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
        @Squares = Square.all
    end
end

erbの方ではマス目が3つごとに<div class="board-row">に囲われる必要があるので、if文で制御してます。

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

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

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

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のヘッダで以下のように指定すると読み込めた。

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

でもこれだと、複数のjsを読み込むことになり、1つに圧縮してリクエストを減らしていた恩恵が受けられなくなる・・・。
image.png

ドウシヨウ

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

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>

うーん。独特すぎて謎汗

サーバー側

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: :indexindex.html.erbを使いまわしています。
判定処理calculateWinnerやレコード追加処理はモデルSquareでやっています。

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

テーブルデータ

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のチュートリアルもやってもっと基礎から理解しよう・・・。

→ 次回

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?