Rails グループ機能の実装①

## 初めに
Railsでグループ機能を実装したのでその実装方法を記事にしてみました。
初学者ですので間違いもあるかと思いますが、その際はご指摘いただけると幸いです。

## 今回の記事でできるようになる事
グループの作成機能
グループの一覧画面作成
自分がグループオーナーの場合はEditリンクを出す
グループ編集機能
グループ詳細画面の作成

## アソシエーションの考え方
ユーザー：Group= 1:N（複数）
Group:ユーザー=1:N（複数）　
ユーザーとグループが多対多の関係になるので中間テーブルが必要となります。

そこで中間テーブル（GroupUser）を用意し、外部キーとしてuser_idとgroup_idを持たせます。
![スクリーンショット 2023-05-28 11.42.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3398256/1f0b935c-0148-5180-363f-c539eb191760.png)

## モデルの作成
```
$rails g model Group
```
```
$rails g model GroupUser
```
作成したマイグレーションファイルを編集します。
```db/migrate/2023~_create_groups.rb
class CreateGroups < ActiveRecord::Migration[6.1]
  def change
    create_table :groups do |t|
      t.string :name
      t.text :introduction
      t.integer :owner_id

      t.timestamps
    end
  end
end
```
```db/migrate/2023~_create_group_users.rb
class CreateGroupUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :group_users do |t|
      t.references :user, index: true, foreign_key: true
      t.references :group, index: true, foreign_key: true

      t.timestamps
    end
  end
end
```

group_userですがt.references :user, index: true, foreign_key: trueのように記述することで
自動的に外部キーとしてuserを持たせることができます。groupも同様です。

記述が済んだら下記を忘れずに行いましょう。
```
$rails db:migrate
```

## モデル内にアソシエーション等を記述する
```app/models/group.rb
class Group < ApplicationRecord
  has_many :group_users, dependent: :destroy
  has_many :users, through: :group_users

  validates :name, presence: true
  validates :introduction, presence: true
  has_one_attached :image

  # imageを呼び出した時に中身が空だったら、assets/images/no_image.jpgを呼び出す
  def get_image
    (image.attached?) ? image : 'no_image.jpg'
  end

end
```

```app/models/group_user.rb
class GroupUser < ApplicationRecord
  belongs_to :user
  belongs_to :group
end
```
```app/models/user.rb
class GroupUser < ApplicationRecord
  has_many :group_users, dependent: :destroy
  has_many :groups, through: :group_users
end
```

## ルーティングを記述
```config/routes.rb
# except destroyアクション以外のルーティングを生成
  resources :groups, except: [:destroy]
```
今回の記事では削除機能は実装していないのでdestroyははずしております。

## groupsコントローラーを作成と記述
まずはgroupsコントローラーを作成しましょう！
```
$rails g controller groups
```
次にコントローラーを編集していきます。
```app/controllers/groups_controller.rb
  before_action :authenticate_user!
  before_action :ensure_correct_user, only: [:edit, :update]

  def index
    @book = Book.new #アプリの都合上定義しておりますご自身のアプリに合わせて変更して下さい。
    @groups = Group.all
  end

  def show
    @book = Book.new #アプリの都合上定義しておりますご自身のアプリに合わせて変更して下さい。
    @group = Group.find(params[:id])
  end

  def new
    @group = Group.new
  end

  def create
    @group = Group.new
    # 誰が作ったグループかを判断する為に必要。
    @group.owner_id = current_user.id
    if @group.save
      redirect_to groups_path
    else
      render 'new'
    end
  end

  def edit
    @group = Group.find(params[:id])
  end

  def update
    if @group.update(group_params)
      redirect_to groups_path
    else
      render "edit"
    end
  end

  private

  def group_params
    params.require(:group).permit(:name, :introduction, :image)
  end

  # params[:id]を持つ@groupのowner_idカラムのデータと自分のユーザーIDが一緒かどうかを確かめる。
  # 違う場合、処理をする。グループ一覧ページへ遷移させる。before_actionで使用する。
  def ensure_correct_user
    @group = Group.find(params[:id])
    unless @group.owner_id == current_user.id
      redirect_to groups_path
    end
  end

end
```

解説
create時の@group.owner_id = current_user.idは
グループの作成者が誰かを判断する為に必要な処理となります。

ensure_correct_userはグループオーナーかどうかを確かめるメソッドになります。
このメソッドを毎回、edit、updateアクション時にbefore actionとして使用しているので
グループオーナーでないとグループ一覧ページに遷移する形になります。

## viewページの作成
まずはグループ作成ページとグループ一覧ページのリンクを作成していきます。
今回はuserの一覧ページにリンクを作成しております。
```app/views/users/index
<div>
  <%= link_to 'グループを作成する', new_group_path %>
  |
  <%= link_to 'グループ一覧', groups_path %>
</div>
```

次にグループ作成のformを作成します。
formは部分テンプレーにて。
```app/views/groups/_form.html.erb
<%= form_with model: group, local: true do |f| %>
  <div class="form-group">
    <%= f.label :グループ名 %>
    <%= f.text_field :name, class: 'form-control' %>
  </div>

  <div class="form-group">
    <%= f.label :紹介文 %>
    <%= f.text_area :introduction, class: 'form-control' %>
  </div>

  <div class="form-group">
    <%= f.label :グループ画像 %>
    <%= f.file_field :image, class: "form-control-file", accept: 'image/*' %>
  </div>

  <div class="form-group">
    <%= f.submit class: 'btn btn-info' %>
  </div>
<% end %>
```
formを呼び出すグループ作成ページを作っていきます。
```app/views/groups/new.html.erb
<div class='container'>
  <div class='row'>
    <div class="col-sm-12 col-md-8 col-lg-5 px-5 px-sm-0 mx-auto">
      <h3>Create Group</h3>
        <%= render 'layouts/errors', obj: @group %>
        <%= render 'form', group: @group %>
    </div>
  </div>
</div>
```

次にグループの一覧ページを作成していきます。
まず、グループ一覧の部分テンプレートを作成します。
```app/views/groups/_index.html.erb
<table class="table table-hover table-inverse">
    <thead>
      <tr>
        <th></th>
        <th>グループ名</th>
        <th>紹介文</th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <% groups.each do |group| %>
        <tr>
          <td><%= image_tag group.get_image, size:"50x50" %></td>
          <td><%= link_to group.name, group_path(group) %></td>
          <td><%= group.introduction %></td>
          <% if group.owner_id == current_user.id %>
            <td>
              <%= link_to 'Edit', edit_group_path(group), class: "btn btn-sm btn-success" %>
            </td>
          <% end %>
        </tr>
      <% end %>
    </tbody>
  </table>
```
作成した一覧ページを呼び出していきます。
```app/views/groups/index.html.erb
<div class='container px-5 px-sm-0'>
  <div class='row'>
    <div class='col-md-3'>
      <h2>User info</h2>
      <%= render 'users/info', user: current_user %>
      <h2 class="mt-3">New book</h2>
      <%= render 'books/form', book: @book %>
    </div>

    <div class='col-md-8 offset-md-1'>
      <h2>Groups</h2>
      <%= render 'index', groups: @groups %>
    </div>
  </div>
</div>
```

ここまできたらもう少しです。
グループ詳細ページを作成していきます。
```app/views/groups/show.html.erb
<div class='container px-5 px-sm-0'>
  <div class='row'>
    <div class='col-md-3'>
      <h2>User info</h2>
      <%= render 'users/info', user: current_user %>

      <h2 class="mt-3">New book</h2>
      <%= render 'books/form', book: @book %>
    </div>

    <div class='col-md-8 offset-md-1'>
      <h2>Group Detail</h2>
      <table class="table table-hover table-inverse">
        <thead>
          <tr>
            <th></th>
            <th>グループ名</th>
            <th>紹介文</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
            <tr>
              <td><%= image_tag @group.get_image, size:"100x100" %></td>
              <td><%= @group.name %></td>
              <td><%= @group.introduction %></td>
              <% if @group.owner_id == current_user.id %>
                <td>
                  <%= link_to 'Edit', edit_group_path(@group), class: "btn btn-sm btn-success" %>
                </td>
              <% end %>
            </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
```

最後に編集ページを作成します。
```app/views/groups/edit.html.erb
<div class='container'>
  <div class='row'>
    <div class="col-sm-12 col-md-8 col-lg-5 px-5 px-sm-0 mx-auto">
      <h3>Editing Group</h3>
      <%= render 'layouts/errors', obj: @group %>
    　　　　<%= render 'form', group: @group %>
    </div>
  </div>
</div>
```

以上で完成になります。
viewページに関しましては今回作成しているアプリの都合上、
グループ機能とは関係ない記述もございますので、
その辺りはご自身のアプリーションに合わせて変更お願い致します。

## 最後に
最後までご覧いただきありがとうございます。
初学者なので、間違っていることや、分かりづらい箇所もあるかと思います。
何かお気づきがあれば遠慮なくご指摘頂けると幸いです。

ここまで長くなりましたが
最後までお付き合いいただきましてありがとうございました！！