1.バージョンを確認しましょう!
Rails 5.1.6
ruby 2.7.2p112
2.完成イメージ
ログイン済みのユーザーで一つのチャットルームを共有し、交流できる機能です。
同じ名前のグループは作成できないようにしてあります。
ぜひclosedな環境でグループを作成し、お話してみてください(^_^)v
3.実装の大きな流れ
1.グループチャット機能に必要なModel、Controller、Viewの実装
2.グループ系モデルとUserモデルの紐付け
3.ちょっとしたフロントエンドデザイン
ちなみにUserモデルは既に作られている前提で記事を書いています。
筆者はdeviseというgemを使ってUser周りを整えております。
ちなみに本記事はこのような目次で進んで行きます!
一緒に楽しんでいきましょう ᕙ( ˙꒳˙ )ᕗ
4. 必要ファイルを作成しましょう
$ rails g controller groups #groups_controller.rbを作成
$ rails g controller chats #chats_controller.rbを作成
$ rails g model group #Group.rbを作成
$ rails g model group_user #Group_user.rbを作成
Groupsテーブルがチャット周りの情報を格納するテーブルになってます✊
このGroupには幾つかのUser情報が含まれるので、
Group_user
という中間テーブルを作ってあげましょう。
上記3つのファイルの作成が終わったら、
少しマイグレーションファイルに記述を書き加えていきましょう✊
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
ちょっとコードの確認をしましょうか。
t.string :name, null: false
はよく見ますよね。groupsテーブルにnameカラムを追加しています。
null: false
は「データがない状態は許さへんで!」ってことです!
t.index :name, unique: true
というこの行。これはユニーク制約と呼ばれるものです。
簡単に述べると「同じデータ」を保存しないということです。
同じグループ名を作ることはできないということですね。
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
モデルを作る際にreferences
を追記することで、指定したモデルとのアソシエーション関係を自動で作ることができます。これはとても便利なのです。
ただ実はこんな感じでターミナルでまとめて記述することもできます。
Groupだけでなく、その中でお話するメッセージもテーブル設計したいので、下記を記述しましょう。
$ rails g model Chat content:string user:references group:references
後はGroupとUserが紐付いているいる必要があるため、下記のようにGroupsテーブルにuser_idを格納できるようにしましょう。
$ rails generate migration AddUserIdToGroups user_id:integer
最後に忘れないうちにマイグレーションをしておきましょう〜!^^
$ rails db:migrate
4.モデルファイルに追記
下記のモデルをこんな感じで編集していきましょう ᕙ( ˙꒳˙ )ᕗ
こんな感じでカラムは入っていませんか??
下記のように直していきましょう!!
has_many :group_users, dependent: :destroy #追加
has_many :groups, through: :group_users, dependent: :destroy #追加
has_many :chats, dependent: :destroy #追加
through
は中間テーブルを通して(through)その先を参照するという事です。
つまり中間テーブルを介してgroupモデル、ないしはuserモデルにアクセスするという意味ですね。
class Group < ApplicationRecord
belongs_to :user
has_many :chats, dependent: :destroy #追加
has_many :group_users, dependent: :destroy #追加
has_many :users, through: :group_users, dependent: :destroy #追加
validates :name, presence: true, uniqueness: true
end
validates :name, presence: true, uniqueness: true
は先程の同じ名前のグループを生成しないようにしています。uniqueness
は独自性という意味ですからね。
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
end
ちなみに、ここまでの作業で自然とUserモデルとGroupモデルのアソシエーションが完了しています。
ぜひ自分の目を駆使しながら、どこでアソシエーションを使っていたか見抜いてみてください👀
5.ルーティングについて
ルーティングは下記を記載しましょう✊
resources :groups, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
resources :chats, only: [:create]
end
ルーティングについて少しおさらいしましょう。
アプリケーション開発していくとRoutingの記述がかなり増えてきて、とても見辛くなってしまいます。
そこでresourcesが登場するのでした。
routingにて特定のコントローラーに対してresoucesを記述することによりindex、create、new、edit、show、update、destroyの標準的な機能を想定したルーティングを設定してくれます。
しかしいま実装したいのは、group_idが1
のURL内で複数のチャット投稿をしていく場合です。
つまりhttps://×××××××/group/1/chat/2
のようなURLになるのでした。
このようなときに上記のようにdo~end
で付帯するresourcesを囲んであげるのですが、このような書き方をRoutingのネストと言います。
6.Controllerの記述
長く書きすぎたので、Controllerはあっさり書こうと思います。
まずchats_controllerから!
class ChatsController < ApplicationController
before_action :authenticate_user!
def create
group = Group.find(params[:group_id])
chat = group.chats.build(chat_params) #buildを使い、contentとtweet_idの二つを同時に代入
chat.user_id = current_user.id
if chat.save
flash[:success] = "コメントしました"
redirect_back(fallback_location: root_path)
else
flash[:success] = "コメントできませんでした"
redirect_back(fallback_location: root_path)
end
end
private
def chat_params
params.require(:chat).permit(:content)
end
end
class GroupsController < ApplicationController
before_action :set_group, only: [:edit, :update]
def index
@groups = Group.all
@group_none = "グループに参加していません。"
end
def new
@group = Group.new
end
def create
@group = Group.new(group_params)
@group.user_id = current_user.id
if @group.save
redirect_to groups_url, notice: 'グループを作成しました。'
else
render :new
end
end
def show
@group = Group.find(params[:id])
@chats = @group.chats
@chat = Chat.new
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 group_params
params.require(:group).permit(:name, user_ids: [])
end
end
@group_none = "グループに参加していません。"
というコードにより、グループがない際はメッセージを出すように変数化しています。
7.Viewの記述
<%= stylesheet_link_tag 'groups', :media => "all" %>
<li><%= link_to "グループを作成する", new_group_path %></li>
<div class="groups-container">
<h3>OpenしているGroup一覧</h3>
<% if @groups == [] %>
<%= @group_none %>
<% else %>
<% @groups.each do |t| %>
<div class="group">
<div class="main-box">
<div class="left-container">
<span>グループ名:<a href="/groups/<%= t.id %>"><%= t.name %></a></span>
<br>
<span>作成時刻: <%= t.created_at %></span>
</div>
<br>
<br>
<% if user_signed_in? && current_user.id == t.user_id %>
<%= button_to "グループを閉じる", group_path(t.id), method: :delete %>
<% end %>
</div>
<% end %>
<% end %>
< /div>
こちらはindex.html.erb
でした。if文を用いて、グループが無い際は文章が表示されるように仕込んであります。
<%= stylesheet_link_tag 'group', :media => "all" %>
<div class="post-container">
<p class="title">チャット作成ツール</p>
<%= form_for(@group, :url => { controller:'groups', action:'create'})do |f| %>
<%= f.label :チャット名 %>
<br>
<%= f.text_field :name,size: 140%>
<br>
<br>
<%= f.submit "チャット作成"%>
<br>
<% end %>
</div>
これはグループ作成ページですね。
<%= stylesheet_link_tag 'group', :media => "all" %>
<h3>グループでチャットしよう!</h3>
<div class="groupinfo">
<p>グループ名:<%= @group.name %></p>
<p>グループ作成日:<%= @group.created_at %></p>
</div>
<div class="comment-wrapper">
<p>チャットルーム</p>
<% @chats.each do |c| %>
<div>
<span>ユーザー名:<%= c.user.name unless c.user.blank? %></span>
<br>
<%= c.content %>
</div>
<br>
<% end %>
<% if user_signed_in? %>
<%= form_with(model: [@group, @chat], local: true) do |f| %>
<%= f.text_area :content %>
<%= button_tag type: "submit" do %>
<i class="far fa-comments"></i> チャットする
<% end %>
<% end %>
<% end %>
</div>
とりあえずなんとか最低限は完成や、、、
8.デザイン
coming soon
9.まとめ
そこまで複雑な機能設計はないので、もっと複雑なものを実装次第Qiitaに取り入れようと思っております。
デザインも記載しますので、ぜひお楽しみに!