【Rails】rubyXLのエクセル出力で部屋に属する所属毎にシートを分けてusersを書き込んでいく

## はじめに
開発で部屋に属する所属毎にシートを分けてエクセル出力する必要があり、ある程度実装ができたのでまとめる。
初学者やrubyXLでエクセル出力を実装しようとしている方々に少しでも参考にもなれば嬉しいのですが、
なんちゃってエンジニアによる自分のアウトプット用の投稿なので、よくないコードの書き方や、または分量的に省略している箇所もございますのでご了承をお願いします。
アドバイスがありましたらコメントを頂けますと幸いです。

## 環境
rubyのバージョン
`ruby 2.7.2p137`
railsのバージョン
`Rails 6.0.3.4`
rubyXLのバージョン
`rubyXL (3.4.25)`

## 出力フロー
roomの詳細ページから出力し、roomに紐づいているユーザー(users)を所属毎(affiliations)にシートを分けて用意してあるエクセルファイルに書き込んで出力します。
usersは中間テーブルのroom_usersを経由して取得します。

## テーブル構図
roomsテーブル(部屋)
|rooms|
|:--|
|id|
|name|

affiliationsテーブル(所属)
|affiliations|
|:--|
|id|
|name|

usersテーブル
|users|
|:--|
|id|
|name|
|affiliation_id|

rooms_usersテーブル
|rooms_users|
|:--|
|id|
|room_id|
|user_id|

## どうしたか
group_byを使い、rooms_usersをrooms_user.user.affiliationでグルーピングし、所属毎(affiliation)に処理を行えるようにしました。
```
@rooms_users.group_by { |rooms_user| rooms_user.user.affiliation }.each do |affiliation, affiliation_rooms_users|
  affiliation_worksheet = workbook.add_worksheet(affiliation.name) # ここで所属毎にシートを作成
## 所属毎の処理を書く
end
```
`@rooms_users.group_by { |rooms_user| rooms_user.user.affiliation }`でrooms_usersをrooms_user.user.affiliationによってグループ化しています。
これにより、同じaffiliationに紐づくroom_userたちが同じグループにまとまり、
`.each do |affiliation, partner_scenes_users|`でgroup_byで生成されたハッシュをループ処理し、affiliation毎にシートを作成できるようになります。
ここら辺の詳しい解説は`group_by`と`Rails ハッシュ　ループ`などで調べてもらえれば理解が進むと思います。

## 全体のコード
```ruby:app/services/excel_generator.rb
class ExcelGenerator
  def initialize(room) # オブジェクトを生成した時に自動で実行されます。引数からroomの変数を作成します。
    @room = room
  end

  def generate
    template_path = Rails.root.join('public/files/template.xlsx') # 配置してあるテンプレートのエクセルファイルのパス
    workbook = RubyXL::Parser.parse(template_path) # テンプレートのエクセルファイルからworkbookを作成する
    worksheet = workbook[0]
    worksheet.add_cell(0,0,@room.name)
    workbook.worksheets.delete_at(0) # 最初のシートを削除 (最初のシートに書き込みがされていないシートが出力されるため。もっと良い解決策はあるかもしれません)
    # roomに関連するユーザー情報を取得
    @rooms_users = @room.rooms_users

    # ここから本題のメイン
    # affiliationごとにシートを分けてデータを書き込む
    @rooms_users.group_by { |rooms_user| rooms_user.user.affiliation }.each do |affiliation, affiliation_rooms_users|
      affiliation_worksheet = workbook.add_worksheet(affiliation.name) # ここで所属毎にシートを作成

      # テンプレートの内容を新しいシートにコピー
      worksheet.each_with_index do |row, row_index|
        row.cells.each_with_index do |cell, col_index|
          affiliation_worksheet.add_cell(row_index, col_index, cell.value) if cell
        end
      end
      # 上記コードでうまくコピーできない場合は以下を試す
      # affiliation_worksheet.sheet_data = worksheet.sheet_data

      affiliation_rooms_users.each_with_index do |rooms_user, index|
        # userを順番に書き込んでいきたいので.each_with_indexでループし処理します
        row = 2 + (index * 3)  # 開始行を調整
        affiliation_worksheet.add_cell(row, 0, room_user.user.name)
      end
    end
    workbook # 呼び出し元にworkbookを返す
  end
end
```

```ruby:app/controllers/rooms_controller.rb
require 'rubyXL'
class RoomsController < ApplicationController
  def generate_xlsx
    @room = Room.find(params[:id])
    excel_generator = ExcelGenerator.new(@room) # 一つ前に作成したExcelGeneratorのオブジェクトを作成
    workbook = excel_generator.generate # generateを実行しエクセルファイルを作成

    respond_to do |format|
      format.xlsx do
        send_data workbook.stream.string, filename: 'example.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: 'attachment' # エクセルファイルを出力
      end
    end
  end
end
```

## 最後に
Railsでのエクセル出力やグルーピングなどの操作はこれまであまりやってこなかったので、今回の実装で理解が進みよかったです。
また、個人の感想ですがエクセル出力はcsvやpdfの出力に比べて大変だと感じました。
ここにはあまり記述していませんが、ただ単にエクセルを出力するだけであれば楽ですが、出力する内容を出力したい箇所に当てはめていく作業などを実装していけば地味に大変です。
ただそれでもRailsのおかげで他の言語に比べて実装しやすいのかなと思います。
この投稿はざっくり作成したので自分以外の方々が見るとわかりずらいかと思いますが、修正箇所が見つかれば隙間時間に編集していこうと思います。