[Ruby]eachメソッドを活用したExcelライクなマトリクス図の作り方


はじめまして、ふろしゅと申します!
railsにてeachメソッドを活用してマトリクス図を作ろうとした際に結構な時間ハマってしまったので、備忘録を兼ねてシェアさせて頂きます。
本記事では前半にマトリクス図の概略とコードを掲載し、後半にコードの解説をしております。

本記事にて取り扱うマトリクス図はどのようなもの?

本記事にて解説するマトリクス図は以下の画像のようなものです。
(画像は原神というゲームにおけるキャラクターの育成素材の対応をマトリクス図でまとめたものとなります)

キャラクターにはそれぞれ見出し列(自由、抗争などの書物)および見出し行(東風の羽などのモンスター素材)のアイテムが育成に必要なアイテムとして設定されており、各セルには必要なアイテムに該当するキャラクターが出力されています。

実際のコードはこちら

matrix_table.html.erb
<table id="MatrixTable" border="1">
  <tr>
    <th></th>
    <% @skills.each do |skill| %>
      <th>
        <%= image_tag skill.image %>
        <br>
        <%= skill.name %>
      </th>
    <% end %>
  </tr>
  <% @bosses.each do |boss| %>
    <tr>
      <th>
        <%= image_tag boss.image %>
        <br>
        <%= boss.name %>
      </th>
      <% @skills.each do |skill| %>
        <td>
          <% Character.where(skill_id: skill.id, boss_id: boss.id).each do |character| %>
            <%= image_tag character.image %>
            <br>
            <%= character.name %>
            <br>
          <% end %>
        </td>
      <% end %>
    </tr>
  <% end %>
</table>

データベース設計とコントローラについて

データベース設計としては、characterテーブル、skillテーブル、bossテーブルの3つが存在しています。
また、characterテーブルはskillテーブルおよびbossテーブルとそれぞれ関連付けされています。
コントローラではデータベースに登録されている要素をeachメソッドで出力するため、それぞれ記述しています。

schema.rb
  create_table "characters", force: :cascade do |t|
    t.string "name"
    t.integer "skill_id"
    t.integer "boss_id"
    t.string "image"
  end

  create_table "bosses", force: :cascade do |t|
    t.string "name"
    t.string "image"
  end

  create_table "skills", force: :cascade do |t|
    t.string "name"
    t.string "image"
  end
controller.rb
def matrix
  @skills = Skill.all
  @bosses = Boss.all
end

コードの解説

上記のコードについて、より詳細に解説を進めていきます。
<table>タグは行を基準に横方向に定義していくという性質のため、eachメソッドを用いてマトリクス図を作っていく場合、2行目以降をどう記述していくかという部分がハマりどころになると考えられます。
(個人的に、2行目以降は1列目でマトリクス図の縦軸の見出しを出力しつつ、2列目以降ではセルの内容を記述していくことになるため複雑でした)

1行目について

1行目について
<tr>
 <!--1行目1列目のセルは空欄のため-->
  <th></th>
  <!--2列目以降はskill素材の画像と名前を繰り返す-->
  <% @skills.each do |skill| %>
    <th>
      <%= image_tag skill.image %>
      <br>
      <%= skill.name %>
    </th>
  <% end %>
</tr>

マトリクス図における1行目、横軸の見出し部分を書いていきます。
skillテーブルに登録されたアイテム群の画像と名前を順番に出力していきますが、最初のセルは空欄とするためには<th></th>をeachメソッドの前に置く必要があります。

2行目以降について

2行目以降・1列目

2行目以降・1列目
<!--2行目以降の処理-->
<% @bosses.each do |boss| %>
  <tr>
    <!--1列目ではboss素材の画像と名前を繰り返す-->
    <th>
      <%= image_tag boss.image %>
      <br>
      <%= boss.name %>
    </th>

1列目ではbossテーブルに登録されたアイテム群を出力させていきます。

2行目以降・2列目以降

2行目以降・2列目以降
<!--2列目以降のセル毎の該当キャラクター抽出のためskillテーブルの情報を与える-->
<% @skills.each do |skill| %> 
  <td>
   <!--boss素材とskill素材が該当するキャラクターを抽出-->
    <% Character.where(skill_id: skill.id, boss_id: boss.id).each do |character| %>
      <%= image_tag character.image %>
      <br>
      <%= character.name %>
      <br>
    <% end %>
  </td>
<% end %>

@skills.each do |skill| は何のために?

2列目以降のセルにはcharacterテーブルに登録されたキャラクターたちの中から、該当するアイテムが設定されているキャラクターをそれぞれマトリクス図の中に当てはめていきます。
whereメソッドを用いてcharacterテーブルから抽出を進めていくため、2列目以降のセルにskillテーブルの情報を与える必要があります。
そのため<td>タグの前にeachメソッドを用いてskillテーブルの情報を出力します。

whereメソッドによるcharacterテーブルからの抽出について

<td>タグ毎にbossテーブルとskillテーブルの情報が入っているので、whereメソッドでcharacterテーブルからそれらの素材に該当するキャラクターを抽出し、画像と名前を出力します。
なお、whereメソッドでは返り値が配列ActiveRecord_Relationというインスタンスとなり、そのままでは出力できないのでeachメソッドで出力する必要があります。
(詳細は下記のリンク先にてまとめられています)

【Rails】whereの戻り値に気を付けようという話
ActiveRecord の find と where の違い。
【続・find と where の違い 】ActiveRecord::Relation を学ぶ。

終わりに

マトリクス図の作成にかなり苦戦したもののeachメソッドを活用したコーディングができました。
ハマった末に<td>タグを1つずつ手作業で書いてみたことで理解が進み、eachメソッドに落とし込むことができました。
最後に、本記事をお読み頂きありがとうございます!