Rails|グループ作成
要件
グループを作成する
ユーザーの一覧画面に 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ファイルの編集
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
グループ名、グループ紹介文、グループ作成者の情報を追加します。
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
というカラムを追加してくれます。
また、自動的にインデックスを作成してくれます。
インデックスについてまとめてくれている記事があったので、貼っておきます。
foreign_key: true
は、外部キー制約を意味します。
この書き方は、references型の時のみ使用可能です。
外部キー制約とは、「親テーブルに存在しないデータを子テーブルが持つことが無いようにするための制約」です。
インデックスと外部キー制約の書き方について、こちらの記事が勉強になりました!
モデルへの記述
次に、モデルへアソシエーションやバリデーション、メソッドを記述していきます。
belongs_to :user
belongs_to :group
has_many :group_users, dependent: :destroy
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
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ビューに、グループ作成ボタンとグループ一覧ボタンを作成します。
<%= link_to "グループを作成する", new_group_path %>|<%= link_to "グループ一覧", groups_path %>
groups/index ビューの作成
<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オプションが使えるようになります。
<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 ビューの作成
<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 ビューの作成
<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>
<%= 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>
これで完成です!!
参照
Discussion