Railsを用いた投稿の申し込み機能とキャンセル機能の実装方法について解説します。
※ユーザー機能と投稿機能は実装済みであることを前提とします。
実装の流れ
大まかな流れとして、以下の手順で実装していきます。
- 投稿と申込者の中間テーブルを作成する
- モデル同士の関連付け(アソシエーション)を行う
- ルーティングを定義する
- コントローラーに申し込み機能とキャンセル機能を定義する
- 申込ボタンのビューを作成する
投稿と申込者の中間テーブルを作成する
投稿は複数人の申込者を持つことができ、ユーザーは複数の投稿に申し込むことができます。
そのため、投稿とユーザーは多対多の関係となり、中間テーブルが必要となります。
また、ここでは同じユーザーの中でも投稿者と申込者を区別するために、申込者をtaker
と定義することにします。
投稿のモデル名をPost
とすると、post_takersという中間テーブルを作れば良いということになります。
中間テーブルを作成するにはまず、ターミナルに以下のコマンドを打ちモデルを作成します。
$ rails g model PostTaker
出来上がったマイグレーションファイルを開き、以下のように記述します。
class CreatePostTakers < ActiveRecord::Migration[6.0]
def change
create_table :post_takers do |t|
t.references :post, foreign_key: true
t.references :taker, foreign_key: {to_table: :users}
t.timestamps
end
end
end
ここでのポイントは、t.references :taker, foreign_key: {to_table: :users}
の部分です。
このように書くと、中間テーブルの外部キーとして申込者であるtaker
という名前を定義しつつ、usersテーブルを参照できます。
参考記事:https://qiita.com/publichtml/items/1fba15d8071fab66d043
モデル同士の関連付け(アソシエーション)を行う
次に、モデルにアソシエーションを記述します。
class User < ApplicationRecord
has_many :post_takers, foreign_key: "taker_id", dependent: :destroy
end
class Post < ApplicationRecord
has_many :post_takers, dependent: :destroy
has_many :takers, through: :post_takers, dependent: :destroy
end
class PostTaker < ApplicationRecord
belongs_to :taker, class_name: 'User', foreign_key: 'taker_id'
validates_uniqueness_of :post_id, scope: :taker_id
end
post_taker.rbのbelongs_to :taker, class_name: 'User', foreign_key: 'taker_id'
は、
このような書き方をすることでUserモデルに紐付いた申込者と関連付けができます。
参考記事:https://qiita.com/gyu_outputs/items/421cc1cd2eb5b39e20ad
また、validates_uniqueness_of :post_id, scope: :taker_id
は、
投稿と申込者の同じ組み合わせが2つ以上登録されないようにするため、このようなバリデーションを記述しています。
ルーティングを定義する
申し込むというアクションをtake
、キャンセルというアクションをcancel
としてルーティングに以下のように記述します。
Rails.application.routes.draw do
resources :posts do
member do
get 'take'
get 'cancel'
end
end
end
上記では、パスに投稿のidを含めるためmemberを使っています。
参考記事:https://qiita.com/hirokihello/items/fa82863ab10a3052d2ff
コントローラーに申し込み機能とキャンセル機能を定義する
コントローラーに以下の記述を追加します。
※ここでは投稿詳細ページに申込ボタンを表示させることを前提として書いていますが、各々の状況に合わせて書き換えてください。
class PostsController < ApplicationController
before_action :set_post
def show
@user = @post.user
end
def take
# 該当の投稿とログイン中のユーザーとの中間テーブルのレコードを作成する
PostTaker.create(post_id: @post.id, taker_id: current_user.id)
# フラッシュメッセージを表示(フラッシュメッセージを表示させない場合は書かなくて大丈夫です)
flash[:notice] = '申し込みが完了しました。'
# 投稿の詳細ページにリダイレクト
redirect_to action: "show"
end
def cancel
# 中間テーブルの中から、該当の投稿とログイン中のユーザーによるレコードを抽出する
post_taker = PostTaker.find_by(post_g_id: @post.id, taker_id: current_user.id)
post_taker.destroy
flash[:notice] = 'キャンセルが完了しました。'
redirect_to action: "show"
end
private
def set_post
@post = Post.find(params[:id])
end
end
申込ボタンのビューを作成する
最後に、申込ボタンのビューを作成したら完成です!
以下のコードはhamlで記述しています。
-# 投稿者とログイン中のユーザーが一致しない場合のみボタンを表示させる
- if @user.id != current_user.id
-# 該当の投稿の申込者の中に、ログイン中のユーザーが含まれているか否かで場合分けする
- if @post.taker_ids.include?(current_user.id)
= link_to "申し込みをキャンセルする", cancel_posts_path(@post.id), data: { confirm: "こちらの投稿の申し込みをキャンセルしますか?" }
- else
= link_to "申し込む", take_posts_path(@post.id), data: { confirm: "こちらの投稿に申し込みますか?" }