[Rails]女性限定イベントを作成する2/2
はじめに
前回の記事ではユーザテーブルに性別カラムを追加してきました。
その理由としては女性限定のイベントを作りたいからです。
イベントテーブルにonly_woman
カラムを追加し、実装していきます。
満たしたい要件はこちらになります:
1. イベント作成画面にOnly womanのチェックボックスにチェックを入れるだけで女性限定イベントを作できる:
→ ユーザーが女性以外の場合、イベント作成時にOnly womanは表示されず、女性限定イベントが作成できない。
2. 女性限定イベントには女性のみが参加できる。
→ 女性以外の場合、女性限定イベントの詳細ページを開いた際に「参加する」ボタンが表示されない。
3. 女性限定イベントは一覧・詳細画面にて「女性限定」が表記される。
環境
Rails 6.1.4.1
ruby 3.0.2
only_woman
カラムを追加する
イベントテーブルに女性限定かそうでないかのいずれかになるのでデータタイプをboolean
にします。
bin/rails generate migration AddOnlyWomanToEvents OnlyWoman:boolean
Running via Spring preloader in process 53267
invoke active_record
create db/migrate/20230624114926_add_only_woman_to_events.rb
class AddOnlyWomanToEvents < ActiveRecord::Migration[6.1]
def change
add_column :events, :only_woman, :boolean, default: false
end
end
bin/rails db:migrate
== 20230624114926 AddOnlyWomanToEvents: migrating =============================
-- add_column(:events, :only_woman, :boolean, {:default=>false})
-> 0.0030s
== 20230624114926 AddOnlyWomanToEvents: migrated (0.0031s) ====================
event.rb
に女性イベントスコープを追加する
Railsにおけるスコープ(Scope)は、Active Recordモデルに対して特定の条件を指定してデータベースクエリを実行するための方法です。スコープを使用することで、一連の条件をまとめて名前付きのメソッドとして定義し、複数の場所で再利用することができます。
スコープは、モデルクラス内で scope
メソッドを使用して定義されます。以下はスコープの例です。
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(5) }
scope :by_author, -> (author) { where(author: author) }
end
上記の例では、Article
モデルに対していくつかのスコープが定義されています。
-
published
:published
カラムがtrue
の記事を取得するスコープ。 -
recent
:created_at
カラムで降順にソートされた最新の5つの記事を取得するスコープ。 -
by_author
: 引数として渡された著者に関連する記事を取得するスコープ。
これらのスコープは、次のように使用することができます。
Article.published # 公開された記事を取得
Article.recent # 最新の5つの記事を取得
Article.by_author("John") # 著者が "John" の記事を取得
スコープはチェーン可能であり、複数の条件を組み合わせることもできます。
Article.published.recent # 公開された最新の5つの記事を取得
Article.published.by_author("John") # 著者が "John" かつ公開された記事を取得
これにより、再利用可能なクエリのセットを定義し、コードの可読性と保守性を向上させることができます。
ユーザが女性であれば女性限定イベントを作れる
イベントscope
とonly_woman
がtrue
になる条件を追加します。
class Event < ApplicationRecord
...
scope :only_woman, -> { where(only_woman: true) }
def female_only?
only_woman
end
def can_attend?(user)
return true unless female_only?
user&.female?
end
end
上記のコードでは、Event
モデルに only_woman
スコープと female_only?
メソッド、can_participate?
メソッドを追加しています。
only_woman
スコープは、only_woman
列が true
のイベントのみを取得するためのスコープです。
female_only?
メソッドは、イベントが女性限定かどうかを判定するためのメソッドです。only_woman
列の値が true
の場合に true
を返します。
can_attend?
メソッドは、指定されたユーザーがイベントに参加できるかどうかを判定するためのメソッドです。女性限定イベントの場合は、指定されたユーザーが女性である場合にのみ参加できるように制約を設けています。女性限定でないイベントの場合は、制約を無視して参加できるようにしています。
これらのメソッドを使うことで、モデル内で女性限定イベントの条件を扱い、必要な制約や表示を設定することができます。
イベントコントローラーを編集する
コントローラー内で、女性ユーザが女性限定のイベントを作成できるようにします。
イベントパラメーターに:only_woman
パラメーターを追加します。
class EventsController < ApplicationController
def create
@event = Event.new(event_params)
# ユーザーが女性かつOnly womanがチェックされた場合のみ、女性限定のイベントとして保存する
if current_user.female? && params[:event][:only_woman] == "1"
@event.only_woman = true
end
## 一行で書く
@event.only_woman = true if current_user.female? && params[:event][:only_woman] == "1"
if @event.save
User.all.find_each do |user|
NotificationFacade.created_event(@event, user)
end
redirect_to event_path(@event)
else
# イベントの作成が失敗した場合の処理
render :new
end
end
private
def event_params
params.require(:event).permit(:title, :content, :only_woman, :held_at, :prefecture_id, :thumbnail)
end
end
create
アクション内で、current_user.female?
で現在のユーザーが女性かどうかを判定し、かつ params[:event][:only_woman]
が "1" である場合にのみ、@event.only_woman
を true
に設定しています。
イベント作成フォームにチェックボックスを追加する
<% if current_user&.female? %>
<div class="mb-3">
<%= f.check_box :only_woman, { value: "true"}, class: 'form-check-label', for:'flexCheckDefault' %>
<%= f.label :only_woman %>
</div>
<% end %>
女性限定のイベントを作ってみて確認します。
irb(main):002:0> event = Event.last
Event Load (0.1ms) SELECT "events".* FROM "events" ORDER BY "events"."id" DESC LIMIT ? [["LIMIT", 1]]
=>
#<Event:0x00007f965c2cfdb8
...
irb(main):003:0> event.only_woman?
=> true
イベント詳細と一覧ページに’女性限定’を表示させる
詳細ページ
<% if @event&.female_only? %>
<span class="badge bg-secondary">女性限定</span>
<% end %>
一覧ページ
<% if event&.female_only? %>
<span class="badge bg-secondary">女性限定</span>
<% end %>
女性イベントの作成が可能になりました。
次は女性限定のイベントに女性ユーザーだけが参加できるようにします。
参加者コントローラーを編集する
参加者コントローラーに女性限定イベントであれば参加するユーザーは女性かどうかを検証する条件を追加します。
女性限定イベントに女性ユーザでない場合、参加処理を中止するようにします。
class Events::AttendancesController < ApplicationController
def create
@event = Event.find(params[:event_id])
if @event.only_woman? && !current_user.female?
redirect_back(fallback_location: root_path, alert: '女性限定のイベントです')
return
end
event_attendance = current_user.attend(@event)
(@event.attendees - [current_user] + [@event.user]).uniq.each do |user|
NotificationFacade.attended_to_event(event_attendance, user)
end
redirect_back(fallback_location: root_path, success: '参加の申込をしました')
end
...
end
イベント詳細ページに女性限定イベントの場合、女性ユーザー以外に「参加する」ボタンが表示されないようにする
<% unless @event.only_woman && !current_user.female? %>
<%= link_to '参加する',
event_attendance_url(@event),
class: 'btn btn-primary',
method: :post,
data: { confirm: '申し込みます' }
%>
<% end %>
女性限定イベント一覧を取得する
要件外ですが、女性限定イベント一覧を取得し表示させたいと思います。
routes.rb
にコレクションを追加する
Rails.application.routes.draw do
...
resources :events do
collection do
get :future
get :past
get :only_woman
end
end
only_woman
メソッドを追加する
イベントコントローラーにRansack
gemを使ってonly_woman
とfuture
の条件を満たす検索結果を返すようにします。
def only_woman
@q = Event.only_woman.ransack(params[:q])
@events = @q.result(distinct: true).includes(:bookmarks, :prefecture, user: { avatar_attachment: :blob })
.order(held_at: :desc).page(params[:page])
@search_path = future_events_path
render :index
end
ビューにタブを追加する
<li class="nav-item" role="presentation">
<a class="nav-link <%= active_if('events#only_woman') %>" href="/events/only_woman" role="tab">女性限定イベント</a>
</li>
終わりに
モデル・コントローラー・ビューそれぞれの役割の理解を深めることができて楽しい実装でした。
Discussion