#1.Railsでのグループ機能実装で躓いたポイントについてまとめてみた
Railsチュートリアルを一通りやった後でオリジナルアプリを作るためにもグループチャット機能とかあったら面白そうだと考えましていざ色々なサイトを巡りながら実装してみましたが、思いの外苦戦してエラーを返されてばかりでした。結局実装しようと思い立ってから実装し終わるまでに色々なサイトを参考にしたにも関わらず丸1日ほどかかってしまいましたので実装までに苦労したポイントや最終的にどんな形で実装したかについて備忘録も兼ねて記事にしようと思います。
#2.必要ファイルの作成
これについてはそこまで苦労しませんでした。先達のやり方を参考に以下のコマンドをターミナルで打ち込み、マイグレーションファイルやモデルファイル、コントローラーを作成することから始めました。
rails g controller groups
rails g model group
rails g model group_user
必要ファイルを作成した後マイグレーションファイルを以下の形でいじります。ここも特に苦労しませんでした。
class CreateGroups < ActiveRecord::Migration[6.1]
def change
create_table :groups do |t|
t.string :name, null: false
t.index :name, unique: true
t.timestamps
end
end
end
class CreateGroupUsers < ActiveRecord::Migration[6.1]
def change
create_table :group_users do |t|
t.references :group, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
修正が終わったら忘れないうちにマイグレーションします。
rails db:migrate
#3.モデルファイルの修正
モデルファイルに必要事項を追加していきます。ここも基本的には先達の方々がやってきた通りにコードを書けば問題ありません。ただし、注意点がひとつあります。開発用データベースとしてMySQLを使っている場合、一部のモデルファイルにある記述をしておかないとエラーを返してきます。他のデータベースを使った場合どうなるかについては私にもわからないですのでなんとも言えないですが…。ご存知の方いましたら是非とも教えていただければと思います。
##3-1.追加箇所
まずは、先達の方々に倣ってモデルファイルに追加していきます。コードの追加の仕方はどのサイトを巡っていても同じでした。
has_many :group_users
has_many :groups, through: :group_users
class Group < ApplicationRecord
has_many :group_users
has_many :users, through: :group_users
validates :name, presence: true, uniqueness: true
end
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
end
これについても既出の情報になります。むしろ先達の方々の方がこれに加えてリレーションの説明とか事細かにしてくれたりしていますのでこのあたりに関しては初学者の私よりも後述の参考サイトを書かれた方々の記事を読んでいただいた方がいいかもしれません。
##3-2.MySQLを使ってたからこそ投げられたエラー
グループを作成したり、編集するだけでしたらこの節で取り上げる修正は不要ですが、グループを削除する処理も追加したい方でなおかつ開発データベースにMySQLを使っている方でしたら以下のファイルにこのような形で修正を加える必要が出ます。
has_many :group_users, dependent: :destroy #追加
has_many :groups, through: :group_users, dependent: :destroy #追加
has_many :group_users, dependent: :destroy #追加
has_many :users, through: :group_users, dependent: :destroy #追加
上記の4行にdependent: :destoryを追加しました。これ追加しとかないと後で削除処理を実際にやってみたらエラーを投げてきます。外部キーを参照しているためデータを消せないとか何とか…。他のデータベースでやったらどうなるか検証する気にはなりませんので詳しい方教えてください。
ですので、削除処理を追加する方は必ずモデルファイルに上記の追記をしておきましょう。
#4.ルーティング
ルーティングについては特筆することはないと思います。コントローラーにどんな関数を実装するかによってここは適宜変えていただければと思います。
resources :groups, only: [:index, :new, :create, :show, :edit, :update, :destroy]
#5.コントローラーの作成
こちらも先達の方々のものを見よう見まねで書き写して実装していきました。最終的なコードはこちらになります。
class GroupsController < ApplicationController
before_action :set_group, only: [:edit, :update]
def index
@group_lists = Group.all
@group_joining = GroupUser.where(user_id: current_user.id)
@group_lists_none = "グループに参加していません。"
end
def new
@group = Group.new
@group.users << current_user
end
def create
@group = Group.new(group_params)
if @group.save
redirect_to groups_url, notice: 'グループを作成しました。'
else
render :new
end
end
def show
@group = Group.find(params[:id])
end
def edit
@group = Group.find(params[:id])
end
def update
@group = Group.find(params[:id])
if @group.update(group_params)
redirect_to groups_path, notice: 'グループを更新しました。'
else
render :edit
end
end
def destroy
delete_group = Group.find(params[:id])
if delete_group.destroy
redirect_to groups_path, notice: 'グループを削除しました。'
end
end
private
def set_group
@group = Group.find(params[:id])
end
def group_params
params.require(:group).permit(:name, user_ids: [])
end
end
実装したのは一覧表示(index)、新規作成(new,create)、詳細表示(show)、編集(edit,update)、削除(destroy)です。ここで躓き始めたわけですが、どんな風に躓いたかは後述します。
#6.ビューの作成
こちらも必要最低限のことしか書いてません。とりあえず動けばいい、装飾は後からすればいい、そう考えていますので…。必要になってくるファイルは一覧表示(index)、グループ作成(new)、グループ編集(edit)、グループ詳細(show)の4つです。ひとまず最低限表示されるように以下の形で書きました。
<div class="chat-group">
<h1>チャットグループ一覧</h1>
<div class="group-search">
<div class="group-search-form">
<%= form_with url: groups_path do |f| %>
<%= f.text_field :keyword, placeholder: "グループ検索", class: "group-search-form-input" %>
<%= f.submit '検索' %>
<% end %>
</div>
<div class="group-new">
<%= link_to 'グループ作成', new_group_path %>
</div>
<% if @group_joining == [] %>
<%= @group_lists_none %>
<% else %>
<div class="group-list">
<ul class="group-list-table">
<% @group_lists.each do |list| %>
<% if list.user_ids.include?(current_user.id) %>
<li class="group-list-table-each-group">
<%= link_to "/groups/#{list.id}" do %>
<%= list.name %>(<%= list.user_ids.count %>)
<%= link_to '編集', edit_group_path(list.id), method: :get %>
<%= link_to '削除', "/groups/#{list.id}", method: :delete %>
<% end %>
</li>
<% end %>
<% end %>
</ul>
</div>
<% end %>
</div>
<div class="user-page">
<%= link_to 'ユーザーページへ', current_user %>
</div>
</div>
<h1>新規作成</h1>
<%= form_with(model: @group, local: true) do |f| %>
<div class="group-new-form">
<div class="gtoup-new-form-name">
<%= f.label :name, 'グループ名' %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.collection_check_boxes :user_ids, User.all, :id, :username %>
</div>
<%= f.submit '新規作成', class: "btn btn-primary" %>
</div>
<% end %>
<h1>グループ編集</h1>
<%= form_with(model: @group, local: true) do |f| %>
<div class="group-new-form">
<div class="gtoup-new-form-name">
<%= f.label :name, 'グループ名' %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.collection_check_boxes :user_ids, User.all, :id, :username %>
</div>
<%= f.submit '更新', class: "btn btn-primary" %>
</div>
<% end %>
<div class="group-show">
<div class="show-group-chat-back">
<span class="group-show-group-chat-back">
<%= link_to groups_path do %>
<
<% end %>
</span>
</div>
<div class="chat-group-show-talk">
トーク
</div>
</div>
ビューの実装は以上です。シンプルにただ表示するだけのものですが、それでもこれだけの量打ち込む必要がありなかなか大変でした。
#7.躓いた点
ただ先達のコードを書き写すだけで動くなら話は簡単でしたが、3つのトラブルのせいで散々回り道させられました。
##7-1.グループが作成されない(permitの中身が空)
最初に躓いたのがこれです。コントローラーは間違いなく正確に書いた、にも関わらずuser_idsの中身が入っていないせいでグループが作成されない問題に直面しました。原因はビューの書き方にありました。最終的には問題の箇所は<%= f.collection_check_boxes :user_ids, User.all, :id, :username %>という形にすることで正常に動いたわけですが、そうなる前はNoNameエラーがただひたすらに返されました。「groupの中身が空ですよ?」だそうです。知らんがな。
この形で動くようになるまではただひたすら:user_idsの部分とコントローラーのStrong Permission(params.require(:group).permit(:name, user_ids: [])のとこ)をいじくり回してました。手探りで動くように試行錯誤しなければいけなかったのは大変でした。
##7-2.メソッドがあらへんよ?(NoMethodError)
グループ作成がどうにか成功したと思ったら今度は編集画面をテストしていてまたもやエラー。editもupdateもパスつなげてあるはずなのにです。コントローラーも何度も先達のコードを確認して書いたにも関わらず「set_groupメソッドがありませんよ?」という理由でNoMethodErrorが帰ってきたわけです。先達のコード色々と見直しても誰もそんなメソッドについて書いている方はいませんでした。
そこで、何が原因かを探るためにもrails g scaffoldでCRUDファイル一式を作成した上でコントローラーのコードを今一度見直し、欠けている関数が何なのかを探ることから始めました。
結論を言ってしまいますと、あっけなく答えは出てきました。privateよりも下に以下のコード追加するだけでよかったんです。
def set_group
@group = Group.find(params[:id])
end
ただこれだけでした。Railsの初歩的すぎるところすっ飛ばしていきなりアプリ作ったりしてましたので完全に盲点でした。そりゃ誰もここについて書かないわけですよ…。知ってて当たり前のことなんでしょうから…。
##7-3.データが消せない!!
最後に躓いたのがグループ削除処理です。モデルファイルへの加筆のところでも少し触れましたが、外部キーを幾重にも使ったリレーションを構築している以上、特定のデータベースを使った場合か、全てのデータベースで起こることかは定かではありませんが、dependent: :destroyを追記しておかないと、外部キーを参照しているインスタンスを削除できない問題があるようです。もしこういった機能の実装を考えてる方がいましたらモデルファイルに追記するのを忘れないようにしましょうね?
#8.最後に
結局実装に1日近くかかってしまったわけですが、いざ動くのを見ていると、やりきった達成感でいっぱいでした。もっとも、こうしたコードとかリレーションを考えてくれてた先達の方々がいたからこそ1日で済んだわけで私一人で最初から作ることを考えたら1日では済まなかったと思います。コードを考えてくれてた先達の方々に感謝します。
###参考にしたサイト
・https://qiita.com/savaniased/items/ce7dd5a825ad0f6be53c
・https://no-idea.hateblo.jp/entry/2020/06/25/135552
・https://yanai-blog.net/entry/%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90%E3%83%BB%E7%B7%A8%E9%9B%86%E6%A9%9F%E8%83%BD%E3%81%AE%E5%AE%9F%E8%A3%85%EF%BC%88%EF%BC%91%EF%BC%89