Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughペアワーク機能を新規追加:モデル/スケジュール/コールバック/コントローラ/ルーティング/ビュー/ヘルパー/通知(notifier/mailer/activity)/スタイル/マイグレーション/テストが導入・更新された。 Changes
Sequence DiagramsequenceDiagram
participant User as ユーザー
participant Browser as ブラウザ(UI)
participant Controller as PairWorksController
participant Model as PairWork
participant Notifications as ActiveSupport::Notifications
participant Delivery as ActivityDelivery
User->>Browser: ペアワーク作成フォーム送信
Browser->>Controller: POST /pair_works (params)
Controller->>Model: PairWork.create(params)
Model->>Notifications: instrument 'pair_work.create' (payload)
Notifications->>Delivery: PairWorkNotifier / WatchForPairWorkCreator invoked
Delivery->>User: メール・サイト通知配信
User->>Browser: 予約(マッチング)実行
Browser->>Controller: POST /pair_works/:id/reservations (reserved_at, buddy_id)
Controller->>Model: pair_work.reserve(params)
Model->>Notifications: instrument 'pair_work.reserve' (payload)
Notifications->>Delivery: PairWorkMatchingNotifier invoked
Delivery->>Watchers: マッチング通知配信
見積もられたコードレビュー工数🎯 4 (Complex) | ⏱️ ~75 分 Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@app/models/pair_work.rb`:
- Line 53: Line 53の columns_for_keyword_search :title, :description 呼び出しは
ransackable_attributes の明示的定義(PairWork#ransackable_attributes
の定義行)によって上書きされるため削除してください; include Searchable は残して after_commit
コールバックや検索ヘルパー等の他機能に影響するのでそのままにし、不要な columns_for_keyword_search 呼び出しだけを取り除いてください。
In `@app/views/pair_works/_form.html.slim`:
- Around line 42-43: `- if pair_work.new_record?`
ブロック内のスケジュールセクションで子要素のインデントがずれているため、`- if pair_work.new_record?` の直下にある
`.form-item` を他の同レベル要素(例:既存の `.form-item` / `.row` と同じ 6→8
スペースの深さ)に合わせてインデントを修正してください。また、チャネル入力の `value: 'ペアワーク・モブワーク1'`
は編集時に既存値を上書きするので、`value:` オプションを削除するか、もしくは値を設定する処理を `if pair_work.new_record?`
条件内に移動して「新規作成時のみデフォルト値をセット」するように修正してください。
🧹 Nitpick comments (28)
app/views/events/_participation.html.slim (1)
23-43:event.capacity > event.participants.countが3回繰り返されています。Line 23(class属性)、Line 26、Line 35 で同じ条件が評価されています。TODOコメントで認識済みのようですが、ローカル変数に結果をキャッシュするか、ヘルパーに移すと可読性とパフォーマンスの両方が改善されます。
♻️ 例: ローカル変数の活用
- elsif event.opening? + - has_vacancy = event.capacity > event.participants.count - .event-main-actions.is-unparticipationed(class="#{event.capacity > event.participants.count ? 'is-available' : 'is-capacity-over'}") + .event-main-actions.is-unparticipationed(class="#{has_vacancy ? 'is-available' : 'is-capacity-over'}") .event-main-actions__body .event-main-actions__description - - if event.capacity > event.participants.count + - if has_vacancy以降の条件分岐でも
has_vacancyを使用できます。db/migrate/20250414013258_add_channel_to_pair_work.rb (1)
1-5: デフォルト値のハードコードについて確認チャンネル名
'ペアワーク・モブワーク1'がDBレベルのデフォルト値としてハードコードされています。チャンネル名が将来変更された場合、別途マイグレーションが必要になります。運用上問題がなければこのままで構いませんが、変更頻度が高い場合はアプリケーション層でのデフォルト設定(モデルのattributeメソッドや定数化)も検討できます。app/views/questions_and_pair_works/_questions_and_pair_works_page_main_header.html.slim (1)
12-19:params[:action]よりもaction_nameの使用を推奨Railsビューでは、現在のアクション名を取得するには
action_nameヘルパーを使うのがイディオマティックです。params[:action]でも動作しますが、action_nameの方がRailsの慣例に沿っており、意図が明確です。♻️ 修正案
- - unless params[:action] == 'new' + - unless action_name == 'new' li.page-header-actions__item = link_to new_question_path, class: 'a-button is-md is-secondary is-block' do i.fa-regular.fa-plus span 質問する - - unless params[:action] == 'index' + - unless action_name == 'index' li.page-header-actions__item = link_to questions_path(target: 'not_solved'), class: 'a-button is-md is-secondary is-block is-back' do | Q&A一覧app/views/pair_works/_form.html.slim (1)
3-5:.form-itemの二重ネストが冗長に見えるLine 4 の
.form-itemの直下にまた.form-item(Line 5)がネストされています。外側の.form-itemはスタイリング上意図的かもしれませんが、他の.form-itemブロック(Line 16, 26, 78)と同じレベルであるべきなら、Line 4 は不要な可能性があります。意図的なレイアウトであれば問題ありません。app/views/application/_global_nav.slim (1)
52-70: 一時的な本番分岐のTODOコメントは適切ですが、重複コードに注意してください。Lines 61-63 と Lines 64-70 の分岐で、
Q&Aラベル部分が重複しています。現時点では一時的な実装のため許容範囲ですが、本番リリース時の削除漏れにご注意ください。config/locales/ja.yml (1)
13-13: 日付フォーマットの追加は問題ありません。
mdw_uniqueとmdw_and_timeのフォーマットがdate.formatsとtime.formatsにそれぞれ追加されています。%-mと%-dでゼロパディングなしの表示になる点も意図通りかと思います。一点確認:Line 25 の
mdw_and_time: '%-m月%-d日(%a)%H:%M'は曜日と時刻の間にスペースがありませんが、6月5日(木)04:00のような表示で意図通りでしょうか?%-m月%-d日(%a) %H:%M(スペースあり)の方が読みやすい可能性があります。Also applies to: 25-25
app/models/concerns/mentioner.rb (1)
27-29:practice_titleのロジックがQuestionと重複しています。Lines 25-26 の
Question分岐と Lines 28-29 のPairWork分岐でpractice_titleの算出ロジックが完全に同一です。現時点では2箇所のみなので許容範囲ですが、今後同様のパターンが増える場合はプライベートメソッドへの抽出を検討してください。app/views/pair_works/_pair_work.html.slim (2)
43-45:pair_work.comments.lengthは全コメントをメモリにロードしますPR目標にN+1対策が含まれていますが、
.lengthは常にコレクション全体をロードして配列の長さを返します。.sizeを使うと、プリロード済みならキャッシュされた値を、未ロードならCOUNT(*)クエリを利用します。♻️ 提案する修正
- | コメント(#{pair_work.comments.length}) + | コメント(#{pair_work.comments.size})
28-29:doブロック内のインデントが4スペースになっていますLine 29 のインデントが親要素から4スペースになっていますが、Slim の慣例では2スペースです。動作上は問題ない可能性がありますが、ファイル内の他の箇所と統一されていません。
♻️ 提案する修正
= link_to pair_work.user, class: 'a-user-name' do - = pair_work.user.long_name + = pair_work.user.long_namedb/fixtures/pair_work_schedules.yml (1)
9-11:2.day→2.days(複数形)に統一プロジェクトの方針として、時間の加算には複数形(
Numeric#hours,Numeric#days等)を優先して用いることになっています。Line 3 の1.dayは単数で自然ですが、2.dayは2.daysに修正した方が一貫性があります。♻️ 提案する修正
pair_work_schedule_3: pair_work: pair_work3 - proposed_at: <%= Time.current.beginning_of_day + 2.day %> + proposed_at: <%= Time.current.beginning_of_day + 2.days %>Based on learnings: 「fjordllc/bootcamp: 時間の加算には Numeric#hours(複数形)を優先して用いるプロジェクト方針」
app/models/pair_work_matching_notifier.rb (1)
7-7: 二重否定をunlessに置き換えると可読性が向上します
return if !exprよりreturn unless exprの方がRubyのイディオムに沿っています。♻️ 提案
- return if !pair_work.saved_change_to_attribute?(:reserved_at, from: nil, to: pair_work.reserved_at) + return unless pair_work.saved_change_to_attribute?(:reserved_at, from: nil, to: pair_work.reserved_at)app/models/pair_work_notifier.rb (1)
15-19:User.mentor.eachでN件の通知を逐次発行している点についてメンター数が増えた場合、
eachループ内でActivityDeliveryを1件ずつ発行するとレスポンスタイムに影響する可能性があります。既存の通知パターンと同様であれば問題ありませんが、将来的にバックグラウンドジョブへの移行を検討する価値はあります。app/models/pair_work_callbacks.rb (1)
4-11:if/elsifの両ブランチが同一処理のため、条件を統合できます。両方のブランチで同じログ出力とキャッシュ削除を行っているため、条件をまとめるとよりシンプルになります。
♻️ 条件統合の提案
def after_save(pair_work) - if pair_work.saved_change_to_attribute?(:published_at, from: nil) - Rails.logger.info '[CACHE CLEARED#after_save] Cache destroyed for unsolved pair work count.' - Cache.delete_not_solved_pair_work_count - elsif pair_work.saved_change_to_attribute?(:reserved_at) || pair_work.saved_change_to_attribute?(:wip) + if pair_work.saved_change_to_attribute?(:published_at, from: nil) || + pair_work.saved_change_to_attribute?(:reserved_at) || + pair_work.saved_change_to_attribute?(:wip) Rails.logger.info '[CACHE CLEARED#after_save] Cache destroyed for unsolved pair work count.' Cache.delete_not_solved_pair_work_count end endtest/helpers/pair_work_helper_test.rb (1)
51-52: テストブロック間に空行がありません。他のテストブロック間には空行がありますが、
learning_time_frame_checked?とschedule_target_timeの間に空行がありません。📝 修正提案
assert_not learning_time_frame_checked?(past_date, my_learning_time_frame_id) end + test 'schedule_target_time' doapp/views/pair_works/_header.html.slim (1)
16-16:pair_work.wipとpair_work.wip?の使い分けが不統一です。Line 17では
pair_work.wip?を使用していますが、Line 16のクラス条件ではpair_work.wipが使われています。動作上は問題ありませんが、Railsの慣例に合わせてwip?に統一するとより明示的です。♻️ 修正案
- h1.page-content-header__title class=(pair_work.wip ? 'is-wip' : '') + h1.page-content-header__title class=(pair_work.wip? ? 'is-wip' : '')app/models/watch_for_pair_work_creator.rb (1)
12-24:watch_recordsメソッドはprivateにすべきです。
watch_recordsはcallの内部実装の詳細であり、外部から呼ばれる必要がありません。同様のクラス(PairWorkNotifier,PairWorkMatchingNotifier)ではprivateセクションにヘルパーメソッドが配置されています。♻️ 修正案
end + private + def watch_records(pair_work)db/migrate/20250203011220_create_pair_works.rb (2)
11-11:wipカラムにデフォルト値の追加を検討してください。
null: falseが設定されていますが、デフォルト値がありません。モデル側で常に設定されるとしても、default: falseを指定しておくとデータベースレベルでの安全性が向上します。
3-14:reserved_atへのインデックス追加を検討してください。
upcoming_pair_worksスコープ等でreserved_atを条件にしたクエリが実行されるため、データ量が増えた場合にインデックスがあるとパフォーマンスが向上します。現段階ではデータ量が少ないと思われるので、必要に応じて後日対応でも問題ありません。app/views/pair_works/_body.html.slim (2)
44-53: 認可制御をCSSクラスis-hiddenのみで行っているのはセキュリティ上の防御層が薄い
is-hiddenCSSクラスでフッター(編集・削除リンク)の表示を制御していますが、HTML自体はすべてのユーザーに配信されます。コントローラー側でset_my_pair_workによる認可チェックがあるためサーバーサイドは保護されていますが、DOMにリンクやCSRFトークンが露出します。Slim条件分岐で未認可ユーザーにはHTML自体をレンダリングしないほうがより安全です。
また、Line 51の
current_user.admin? || pair_work.user == current_userは Line 44の条件と同一であり、フッター内にいる時点で常に真になるため冗長です。♻️ 提案
- footer.card-footer class=(current_user == pair_work.user || current_user.admin? ? '' : 'is-hidden') + - if current_user == pair_work.user || current_user.admin? + footer.card-footer
88-92: TODOコメント: ペア確定取り消し機能が未実装Line 90-92で「ペア確定を取り消す」ボタンが
div要素として描画されていますが、リンク先が未設定です。PR説明によると#8679で対応予定とのことですが、現状このボタンをクリックしても何も起こりません。ユーザーの混乱を避けるため、disabled属性の追加またはボタン自体の非表示を検討してください。必要であれば、disabled状態のボタン実装を提案できます。
app/helpers/pair_work_helper.rb (2)
8-17:sorted_wdaysはモジュラ演算で簡潔に書ける現在のループは正しいですが、同等のロジックをワンライナーで表現できます。
♻️ 簡潔な実装案
def sorted_wdays(date) - max_wday = 6 - sorted_wdays = [date.wday] - max_wday.times do - next sorted_wdays << 0 if sorted_wdays.last == max_wday - - sorted_wdays << sorted_wdays.last + 1 - end - sorted_wdays + (0..6).map { |i| (date.wday + i) % 7 } end
19-25:disabled?メソッドのcurrent_user依存に注意このヘルパーはビューコンテキストの
current_userに暗黙的に依存しています。テスト(test/helpers/pair_work_helper_test.rb)ではcurrent_userをusers(:kimura)としてスタブしていますが、ヘルパーメソッドの署名からこの依存が見えないため、将来的にDecoratorへの移行やユニットテスト時に問題になる可能性があります。現時点では既存パターンに沿っているため問題ありませんが、留意点として記録します。
test/system/pair_works_test.rb (2)
26-28: Choices.jsのセレクタがハードコーディングされており脆弱
#choices--js-choices-practice-item-choice-12は Choices.js が自動生成するIDであり、フィクスチャのデータ順やライブラリバージョンの変更で壊れる可能性があります。テキストマッチ(
text: 'sshdでパスワード認証を禁止にする')が既に併用されているため、IDが変わった場合でもテストが意図通りの要素を選択するか確認が必要です。
108-125: 認可テストはビュー層のみのカバレッジこのテストは表示の有無を検証していますが、直接URLアクセスによる
edit/destroyのサーバーサイド認可は検証されていません。コントローラーテストまたは追加のシステムテストで、未認可ユーザーがedit_pair_work_pathに直接アクセスした場合の挙動(リダイレクトや404)もカバーすることを推奨します。app/controllers/pair_works_controller.rb (1)
18-21:showアクションでN+1クエリの懸念
@pair_work = PairWork.find(params[:id])では関連テーブルがeager loadされていません。ビュー(_body.html.slim)ではpair_work.buddy、pair_work.user、pair_work.practice、pair_work.schedulesにアクセスするため、個別クエリが発生します。♻️ Eager loading の追加案
def show - `@pair_work` = PairWork.find(params[:id]) + `@pair_work` = PairWork.includes(:user, :buddy, :practice, :schedules).find(params[:id]) `@comments` = `@pair_work.comments.order`(:created_at) enddb/schema.rb (1)
561-577:reserved_atカラムにインデックスの追加を検討
PairWorkモデルではsolved/not_solved/not_heldスコープがreserved_atをWHERE条件に使用しています。またby_targetスコープやキャッシュカウント(Cache.not_solved_pair_work_count)でも頻繁にクエリされます。データ量が増加した際のパフォーマンスを考慮して、reserved_atへのインデックス追加を検討してください。app/models/pair_work.rb (2)
45-51:upcoming_pair_worksスコープのインデント・スタイルを修正lambda本体のインデントが深すぎて可読性が低下しています。
♻️ インデント修正案
- scope :upcoming_pair_works, lambda { |user| - now = Time.current - within_day = now...(now + 3.days) - PairWork.where(user_id: user.id).or(PairWork.where(buddy_id: user.id)) - .solved - .where(reserved_at: within_day) - } + scope :upcoming_pair_works, lambda { |user| + now = Time.current + within_day = now...(now + 3.days) + where(user_id: user.id).or(where(buddy_id: user.id)) + .solved + .where(reserved_at: within_day) + }なお、スコープ内では
PairWork.where(...)の代わりにwhere(...)を使用できます(スコープのコンテキストは暗黙的にモデルクラスです)。
20-22: コールバックインスタンスの共有を検討
PairWorkCallbacks.newが3回呼ばれ、3つの別インスタンスが生成されています。PairWorkCallbacksはステートレスなので動作に問題はありませんが、1つのインスタンスを共有するとわずかにクリーンです。♻️ 共有インスタンスの使用案
+ CALLBACKS = PairWorkCallbacks.new + - after_save PairWorkCallbacks.new - before_destroy PairWorkCallbacks.new, prepend: true - after_destroy PairWorkCallbacks.new + after_save CALLBACKS + before_destroy CALLBACKS, prepend: true + after_destroy CALLBACKS
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/views/application/_global_nav.slim`:
- Around line 48-71: The nav item's active-state regex only matches questions
paths (current_link(/^questions/)) so it stays inactive for pair_work pages;
update the call to include pair_works as well (e.g.
current_link(/^(questions|pair_works)/) or an equivalent regex) so the link
becomes active on both questions and pair_works routes; adjust the usage near
the pair_work_available? branching and references like
Cache.not_solved_pair_work_count to ensure consistent behavior.
🧹 Nitpick comments (4)
app/models/pair_work.rb (2)
20-22: コールバックごとにPairWorkCallbacks.newが生成される点について
after_save、before_destroy、after_destroyそれぞれで新しいインスタンスが生成されます。状態を持たないコールバッククラスであれば、定数として1つのインスタンスを共有する方が効率的です。♻️ 改善案
+ PAIR_WORK_CALLBACKS = PairWorkCallbacks.new + - after_save PairWorkCallbacks.new - before_destroy PairWorkCallbacks.new, prepend: true - after_destroy PairWorkCallbacks.new + after_save PAIR_WORK_CALLBACKS + before_destroy PAIR_WORK_CALLBACKS, prepend: true + after_destroy PAIR_WORK_CALLBACKS
47-53: スコープ内でPairWork.whereを使用するとスコープチェーンが無視される
scopeのラムダ内でPairWork.where(...)を使うと、呼び出し元で構築済みのリレーションチェーンが無視され、常にPairWork.allを起点とした新しいクエリが生成されます。where(...)を直接使う方がスコープの合成に対して安全です。♻️ 改善案
scope :upcoming_pair_works, lambda { |user| - now = Time.current - within_day = now...(now + 3.days) - PairWork.where(user_id: user.id).or(PairWork.where(buddy_id: user.id)) - .solved - .where(reserved_at: within_day) - } + now = Time.current + within_day = now...(now + 3.days) + where(user_id: user.id).or(where(buddy_id: user.id)) + .solved + .where(reserved_at: within_day) + }app/views/pair_works/_form.html.slim (2)
4-5:.form-itemの二重ネストは意図的ですか?Line 4 と Line 5 で
.form-itemが二重にネストされています。外側の.form-item(Line 4)は子要素を包むだけで、内側の.form-item(Line 5)が実際のコンテンツを持ちます。不要なラッパーであれば削除を検討してください。
74-77:check_box_tagは未チェック時に値を送信しない点に注意
check_box_tagはf.check_boxと異なり、hidden field が自動生成されないため、未チェックの場合はパラメータに含まれません。スケジュール作成ではチェックされた日時のみ送信する意図なので問題ありませんが、編集画面でスケジュールの変更が必要になった場合(将来的にnew_record?条件を外す場合)、既存スケジュールの削除ができなくなるリスクがあります。現時点ではif pair_work.new_record?で制御されているため問題ありません。
_formのform_withの中にscheduleが入っていることで、レコードのupdate時に相手確定してしまう不具合が発生していた。 なのでedit画面の_formではscheduleを表示しないようにした(どちらにせよ仕様でscheduleは更新出来ない)
| @@ -0,0 +1,7 @@ | |||
| # frozen_string_literal: true | |||
|
|
|||
| module PairWorkDecorator | |||
| @@ -0,0 +1,23 @@ | |||
| - important = upcoming_pair_work.reserved_at.beginning_of_day == Time.current.beginning_of_day | |||
There was a problem hiding this comment.
booleanをpartial独自の変数として定義するより何らかのメソッドやヘルパーにするほうが良いように思いました。(極力viewにロジックが無い方がいいので)
spanのクラス名の部分と合わせてhelperとかにするといいかもとおもいました。
There was a problem hiding this comment.
meta_label_by_statusとしてhelperに切り出しました!
db/fixtures/pair_work_schedules.yml
Outdated
| @@ -0,0 +1,11 @@ | |||
| pair_work_schedule_1: | |||
There was a problem hiding this comment.
| pair_work_schedule_1: | |
| pair_work_schedule1: |
他と合わせるとこっちのほうがいいかもです。
| = render 'reactions/reactions', reactionable: pair_work | ||
|
|
||
| hr.a-border-tint | ||
| footer.card-footer class=(current_user == pair_work.user || current_user.admin? ? '' : 'is-hidden') |
There was a problem hiding this comment.
極力viewにロジックが無い方がいい
| footer.card-footer class=(current_user == pair_work.user || current_user.admin? ? '' : 'is-hidden') | |
| footer.card-footer class=(pair_work.owner_or_admin?(current_user) ? '' : 'is-hidden') |
ここもこんな感じでメソッド化してしまおうと考えているんですが、これはやりすぎでしょうか?
current_user == object.user || current_user.admin?というようなコードは既存のコードでもよく見ますし、メソッド化してもコードの長さはそんなに変わっていません。
ですがこうすれば他のモデルでも同じ形で実装出来そうですし、メソッド化することで確実にわかりやすくなるかなと思いました。これだとどっちが良いでしょうか?
There was a problem hiding this comment.
@mousu-a このレベルでメソッド化するとこのサイト全体で膨大な数のメソッドが作成されることになるのでここまではやらなくてもいいかな〜と思いました。
There was a problem hiding this comment.
ありがとうございます!
ここはそのままにしておきます🙏
365c94f to
0003da2
Compare
0003da2 to
0315198
Compare
時間の固定は良いことだと思います。 |
境界値をテストするように
|
@komagata
この変更は 以下の認識のすり合わせをさせていただきたいです🙇♂️ |
|
@mouse-a
はい。 |
| assert learning_time_frame_checked?(future_date, my_learning_time_frame_id) | ||
| assert_not learning_time_frame_checked?(past_date, my_learning_time_frame_id) | ||
| end | ||
| test 'schedule_target_time' do |
There was a problem hiding this comment.
すいません、漏れでした🙏
修正しました!
Issueの作成ありがとうございます。とってもいいとおもいます〜 |
こちらのPRが肥大化したため作り直しました🙏
Issue
概要
現在ディスコードで行っているペアプロ(ペアワーク)の募集、申し込みをbootcampアプリ上で行える機能を実装しました!
仕様をこちらのページにまとめています。
このPRでやること
このPRでやらないこと
titleやdescriptionなどは変更できますが、スケジュールは変更できないようになっています。このPRでやらないことに関してはこちらのissueで対応します。
変更確認方法
feature/support_pair_workをローカルに取り込むgit fetch origin feature/support_pair_work_latestgit checkout feature/support_pair_work_latestbin/devでローカルサーバーを立ち上げる。kimuraでログイン主な活動予定時間セクションにて、(月)3:00、(水)4:00、(木)3:00をチェックし、ページ下部の"更新する"ボタンをクリック
new-mentor、pjord,komagata,unadmentor,machidanohimitsu,mentormentaro)に通知が来ていることを確認komagataで再度ログイン(通知の確認もしたいためメンターが良いです)kimura)、メンター(敬称略:new-mentor、komagata,machidanohimitsu,mentormentaro,unadmentor,pjord)にペア確定の通知が来ていることを確認rails db:seedを実行し、kimuraで再ログイン、ダッシュボードにアクセスScreenshot
変更前
新機能なので変更前のスクリーンショットはありません!
変更後
一覧




詳細
詳細ペア確定後
近日開催のペアワーク
Summary by CodeRabbit
New Features
Style