グループごとに投稿できる機能

こんにちは。寒すぎて凍えているGeeksalonの限界メンターです。

「アーティストごとにアルバムを投稿したい」、「飲み会ごとに飲んだ量でランキングを実装したい」、「旅行プランごとに行き先を登録したい」etc...

そんな声に応えた機能を実装してみました。ユーザーのグループ機能はあったんですが、投稿をグループ化するのはぱっと見つからなかったので、初めてですが記事を書いてみました。未熟者ではありますが、温かい目で読んでみてください♪

---

## 【目次】
1. 開発環境
2. 概要
3. アソシエーション
4. 機能実装
　➢ モデルの作成
　➢ コントローラーの作成
　➢ アソシエーション
　➢ HTMLの実装
    　　・ おまけ：ランキング


---
## 【 1. 開発環境 】
・ruby 3.0.4
・rails 6.1.6

**- 前提 -**
・Userの機能は実装済み
・TweetのCRUD機能は実装済み

---
## 【 2. 概要 】
- コントローラー・モデルは以下3つ
    - User：ユーザー
    - Tweet：投稿(好きなものに置き換えて実装してください)
    - Room：グループ(Groupは予約語なのでRoomで実装しています)
- ユーザーは自由にルームを作成できる
- 投稿の際にルームを登録する。投稿は1つのルームに所属する。
- ユーザーは複数ルームに所属する。
    - 今回の実装では、複数回所属してしまっている状態。これを逆手にとって後述のランキングを実装している。
    - おいおいルームのページから投稿するボタンを実装して自動的にそのルームでの投稿になるようにしたい。

 __- 完成図 -__
・ ルーム一覧ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072402/05acc7ee-86ff-9379-3bb2-8c6acb57f680.png)
・ ルーム詳細ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072402/6bee2c86-eaa9-7534-3ba8-a9f0b0976c4c.png)





---

## 【3. アソシエーション】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072402/c7561594-87a8-c17a-4cee-44b341acc9b2.png)

- UserとTweetは1対多
- RoomとTweetは1対多
- UserとRoomは多対多
    - Tweetが中間テーブルとなってくれます。
---

## 【4. 機能実装】
__➢ モデルの作成__
　まずRoomモデルを作成しましょう。
```terminal:terminal
$ rails g model Room roomname:text
$ rails db:migrate
```

__➢ コントローラーの作成__
　コントローラーを作成します。
```terminal:terminal
$ rails g controller rooms
```
できたら中身に以下を記述していきましょう。
```rb:rooms_controller
class RoomsController < ApplicationController

    def index
        @rooms = Room.all
    end

    def new
        @room = Room.new
    end

    def create
        room = Room.new(room_params)

        if room.save
            redirect_to :action => "index"
        else
            redirect_to :action => "new"
        end
    end

    def show
        @room = Room.find(params[:id])
        @users = User.all
        
        #ランキング実装部分
        @roomtweets = Room.find(params[:id]).tweets
        @ranks = User.find(@roomtweets.group(:user_id).order('count(id) desc').pluck(:user_id))
    end
  
    private
      def room_params
        params.require(:room).permit(:roomname)
      end
end
```
tweet投稿時にRoomを割り振れるように、tweet_controllerも書き加えます。
```rb:tweets_controller
 # ~ 省略 ~

def new
        @tweet = Tweet.new
        rooms = []
        #tweet新規投稿の際、Roomを選択できるように配列にしてプルダウンに
        Room.all.each do |r|
            room = []
            room.push(r.name)
            room.push(r.id)
            rooms.push(room)
        end
        @rooms = rooms
    end

 # ~ 省略 ~

 private
    def tweet_params
        params.require(:tweet).permit(:body, :room_id)
    end

 # ~ 省略 ~
```
__➢ アソシエーション__
Tweetsテーブルにroom_idカラムを追加します。
```terminal:terminal
$ rails g migration AddRoomIdToTweets room_id:integer
$ rails db:migrate
```
アソシエーションを記述していきます。
```rb:user.rb
class User < ApplicationRecord
  has_many :rooms, through: :tweets

  #Tweetsテーブルを介してRoomを関連付ける
  has_many :joined_rooms, through: :tweets, source: :room

  # 所属しているRoomを表示する際、使用する
  def already_joined?(room)
    self.tweets.exists?(room_id: room.id)
  end
end
```
```rb:tweet.rb
class Tweet < ApplicationRecord

    belongs_to :user
    belongs_to :room

end
```
```rb:room.rb
class Room < ApplicationRecord
  has_many :tweets
  
  #tweetsテーブルを介してUserを関連付ける
  has_many :users, through: :tweets
  #Tweetsテーブルを介して所属しているUserを関連付ける
  has_many :joined_users, through: :tweets, source: :user

  #所属しているユーザーを表示する際、使用する
  def already_joining?(user)
    self.tweets.exists?(user_id: user.id)
  end
end
```

__➢ HTMLの実装__
まずはルーティングを記述しておきましょう。
```rb:routes.rb
  resources :rooms
```
以下はRoom一覧ページです。
```erb:views/rooms/index.html.erb
<div class="rooms-container">
  <%= link_to "new", new_room_path %> #roomの新規作成
  <br>
  <% @rooms.each do |r| %> #roomの一覧表示
    ・<%= link_to  room_path(r.id) do %><%= r.roomname %><% end %><br>
  <% end %>
</div>
```
つぎはRoom新規作成ページです。
```erb:views/rooms/new.html.erb
<%= form_for(@room, :url => { controller:'rooms', action:'create'}) do |f| %>
  <%= f.label :room名 %>
  <%= f.text_field :roomname, :size => 140, class: "imput" %>
  <%= f.submit %>
<% end %>

<%= link_to "Room一覧に戻る", rooms_path %>
```
そして、Roomの詳細ページです。
```erb:views/rooms/show.html.erb
<%= link_to "rooms一覧に戻る", rooms_path %>

<br>

<h4>room名：<%= @room.roomname %></h4>

<% @users.each do |u| %>
  <% if u.already_joined?(@room) %>
    <%= u.name %><br>
  <% end %>
<% end %>

<br>
<h4>メンバーの投稿</h4>
<% @room.tweets.each do |t| %>
  〇<%= t.body %>
  /<%= t.user.name %><br>
<% end %>

<br>

<h4>Room内 投稿数ランキングTop3</h4>
<% @ranks.each.with_index(1) do |u, i| %>
  第<%= i %>位：<%= u.name %>✨<br>
<% end %>
```
そして最後に、tweetの投稿ページも少し手を加えておきましょう。
```erb:views/tweets/new.html.erb
<%= form_for(@tweet, :url => { controller:'tweets', action:'create'}) do |f| %>

　<%# 中略 %>

<%# Roomの登録 %>
    <%= f.label :room名 %>
    <%= f.select :room_id, @rooms %>

　<%# 中略 %>

<% end %>
```
---
### まとめ
以上がグループ機能実装の簡単な説明です。基本的にはグループチャットの原理を利用していることが分かるかと思います。
しっかり細かいところまで考えられていない＆実装も中途半端なので、今後詰めていければと思っています。