🙆

Rails|グループ作成

2023/08/06に公開

要件

グループを作成する
ユーザーの一覧画面に group を作成するボタンを作る
グループを作成する機能の実装
作成した人がグループオーナーになる
グループ一覧画面を作成
自分がグループオーナーの場合は、Edit リンクを出す
ユーザーの一覧画面に group 一覧のボタンを作る
グループ編集機能を作成
グループオーナーだけ編集可能にする
グループ詳細画面の作成(メンバーはまだ実装しなくて良い)

開発環境

ruby 3.1.2p20
Rails 6.1.7.4
Cloud9

前提

Bookモデル、Userモデルは作成済み

全体像

1User は多くの Groupに所属可能。
1Group は多くの Userを持つことが可能。
→ User vs Group が 多対多 の関係になってしまうので、
 中間テーブル GroupUser を作成します。

モデルを作成

$ rails g model group 
$ rails g model group_user user:references group:references

migrationファイルの編集

20xx_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

グループ名、グループ紹介文、グループ作成者の情報を追加します。

20xx_create_group_users.rb
class CreateGroupUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :group_users do |t|
      t.references :user, foreign_key: true
      t.references :group, foreign_key: true

      t.timestamps
    end
  end
end
rails db:migrate

t.references:user とすると、user_idというカラムを追加してくれます。
また、自動的にインデックスを作成してくれます。
インデックスについてまとめてくれている記事があったので、貼っておきます。
https://qiita.com/seiya1121/items/fb074d727c6f40a55f22

foreign_key: true は、外部キー制約を意味します。
この書き方は、references型の時のみ使用可能です。
外部キー制約とは、「親テーブルに存在しないデータを子テーブルが持つことが無いようにするための制約」です。

インデックスと外部キー制約の書き方について、こちらの記事が勉強になりました!
https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47

モデルへの記述

次に、モデルへアソシエーションやバリデーション、メソッドを記述していきます。

group_user.rb
  belongs_to :user
  belongs_to :group
user.rb
  has_many :group_users, dependent: :destroy
group.rb
  has_many :group_users, dependent: :destroy
  belongs_to :owner, class_name: "User"

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

  def get_group_image
    (group_image.attached?) ? group_image : 'no_image.jpg'
  end

  def is_owned_by?(user)
    owner.id == user.id
  end

メソッドの部分を解説します。

  def get_group_image
    (group_image.attached?) ? group_image : 'no_image.jpg'
  end

このメソッドは、group_imageがアタッチされている(存在する)かどうかをチェックします。もしgroup_imageがアタッチされているなら、それを返します。もしgroup_imageがアタッチされていないなら、'no_image.jpg'という文字列を返します。

(group_image.attached?) ? group_image : 'no_image.jpg'
この部分は、if文を1行で書いています。

if group_image.attached?
  group_image
else
  'no_image.jpg'
end

このように書き換えることも可能です。

  def is_owned_by?(user)
    owner.id == user.id
  end

この部分は、引数のuserが Groupの作成者かどうかを調べるメソッドです。
Groupの作成者にのみ Groupの編集画面を表示させるとき、などに使用します。

ルーティングへの記述

resources :groups, only: [:new, :index, :show, :create, :edit, :update]

コントローラを作成

$ rails g controller groups new index show edit
groups_controller.rb
  before_action :authenticate_user!
  before_action :ensure_correct_user, only: [:edit, :update]

  def index
    @book = Book.new
    @groups = Group.all
    @user = User.find(current_user.id)
  end

  def show
    @book = Book.new
    @group = Group.find(params[:id])
    @user = User.find(current_user.id)
  end

  def new
    @group = Group.new
  end

  def create
    @group = Group.new(group_params)
    @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, :group_image)
    end

    def ensure_correct_user
      @group = Group.find(params[:id])
      unless @group.owner_id == current_user.id
        redirect_to groups_path
      end
    end

@user や @book は別で作成しているレンダリングファイル用なので、無視してくださいorz

users/index ビューの作成

users/indexビューに、グループ作成ボタンとグループ一覧ボタンを作成します。

users/index.html.erb
<%= link_to "グループを作成する", new_group_path %>|<%= link_to "グループ一覧", groups_path %>

groups/index ビューの作成

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 partial: "users/info", locals: { user: @user }%>

      <h2 class="mt-3">New Book</h2>
      <%= render partial: "books/form", locals: { book: @book } %>
    </div>

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

  </div>
</div>

<%= render partial: "users/info", locals: { user: @user }%>
この部分は、<%= render "users/info", user: @user %> と同じ動きをします。
ただ、上記のように書くことによって、collectオプションが使えるようになります。
https://himakuro.com/rails-render-collection

groups/_index.html.erb
<table class="table">
  <thead>
    <tr>
      <th>image</th>
      <th>name</th>
      <th colspan="3"></th>
    </tr>
  </thead>
  <tbody>
    <% groups.each do |group| %>
    <tr>
      <td><%= image_tag group.get_group_image, size: "50x50" %></td>
      <td><%= link_to group.name, group_path(group) %></td>
      <td><%= link_to "Show", group, class: "group_#{group.id}" %></td>
      <% if group.is_owned_by?(current_user) %>
        <td><%= link_to "Edit", edit_group_path(group), class: "btn btn-success group_#{group.id}" %></td>
      <% end %>
    </tr>
    <% end %>
  </tbody>
</table>

groups/show ビューの作成

groups/show.html.erb
<div class="container px-5 px-sm-0">
  <%= render 'layouts/errors', obj: @book %>
  <div class="row">

    <div class="col-md-3">
      <h2>User info</h2>
      <%= render partial: "users/info", locals: { user: @user }%>

      <h2 class="mt-3">New Book</h2>
      <%= render partial: "books/form", locals: { 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 colspan="3"></th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><%= image_tag @group.get_group_image, size: "50x50" %></td>
            <td><%= @group.name %></td>
            <td><%= @group.introduction %></td>
            <td>
              <% if @group.owner_id == current_user.id %>
                <%= link_to "Edit", edit_group_path(@group), class: "btn btn-success" %>
              <% end %>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

  </div>
</div>

groups/new ビューの作成

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>New Group</h3>
      <%= render 'layouts/errors', obj: @group %>
      <%= render 'form', group: @group %>
    </div>
  </div>
</div>
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 :group_image, class: "form-control-file", accept: "image/*" %>
  </div>

  <div class="form-group">
    <%= f.submit class: "btn btn-info" %>
  </div>

<% end %>

groups/edit ビューの作成

<div class="container">
  <div class="row">
    <div class="col-sm-12 col-md-8 col-lg-5 px-5 px-sm-0 mx-auto">
      <h2>Editing Group</h2>
      <%= render 'layouts/errors', obj: @group %>
      <%= render 'form', group: @group %>
    </div>
  </div>
</div>

これで完成です!!

参照

https://zenn.dev/goldsaya/articles/f32a3b3186084f

Discussion