Skip to content

MentorsWatchForQuestionCreator及び他1クラスのnewspaperをActiveSupport::Notificationsに移行#8987

Merged
komagata merged 4 commits intomainfrom
chore/convert-mentorwatch-and-aianswer-to-notifications
Aug 25, 2025
Merged

MentorsWatchForQuestionCreator及び他1クラスのnewspaperをActiveSupport::Notificationsに移行#8987
komagata merged 4 commits intomainfrom
chore/convert-mentorwatch-and-aianswer-to-notifications

Conversation

@sjabcdefin
Copy link
Copy Markdown
Contributor

@sjabcdefin sjabcdefin commented Jul 26, 2025

Issue

概要

以下の2クラスの実装をnewspaperからActiveSupport::Notificationsに置き換えました。

  • MentorsWatchForQuestionCreator
  • AIAnswerCreator

変更確認方法

1. ブランチの取り込みとサーバー起動

  1. ブランチ chore/convert-mentorwatch-and-aianswer-to-notifications をローカルに取り込む
  2. foreman start -f Procfile.devでサーバーを立ち上げ

2. 動作確認

質問作成時の動作確認

  1. 任意のユーザでログインする
  2. 左サイドバーの[Q&A]をクリックする
  3. Q&A一覧画面右上の[+質問する]ボタンをクリックする
  4. プラクティス・タイトル・質問を記入し、[登録する]ボタンをクリックする
  5. 以下の各メンターユーザでログインし、それぞれ次項以降の確認を実施
  • komagata
  • machida
  • mentormentaro
  • unadmentor
  • pjord
  1. MentorsWatchForQuestionCreatorの確認として、以下を確認する
  • ダッシュボード画面の[Watch中]タブをクリックし、先ほど作成した質問が表示されること
  1. AIAnswerCreatorの確認として、以下を確認する。なお、任意のメンターユーザで一回確認すればOK。
  • ダッシュボード画面の[Watch中]タブをクリック後、先ほど作成した質問をクリックし、AIによって生成された解答が「仮の解答です。」であること

質問WIP時の動作確認

  1. 任意のユーザでログインする
  2. 左サイドバーの[Q&A]をクリックする
  3. Q&A一覧画面右上の[+質問する]ボタンをクリックする
  4. プラクティス・タイトル・質問を記入し、[WIP]ボタンをクリックする
  5. 任意のメンターユーザでログインする
  6. ダッシュボード画面の[Watch中]タブをクリックし、先ほどWIPした質問が表示されないことを確認する

質問をWIPから公開した時の動作確認

  1. 先ほど質問をWIPしたユーザでログインする
  2. 左サイドバーの[Q&A]をクリックする
  3. Q&A一覧画面の[全て]をクリックし、先ほどWIPした質問をクリックする
  4. [内容修正]ボタンを押下し、[質問を公開]ボタンをクリックする
  5. 以下の各メンターユーザでログインし、それぞれ次項以降の確認を実施
  • komagata
  • machida
  • mentormentaro
  • unadmentor
  • pjord
  1. MentorsWatchForQuestionCreatorの確認として、以下を確認する
  • ダッシュボード画面の[Watch中]タブをクリックし、先ほど公開した質問が表示されることを確認する
  1. AIAnswerCreatorの確認として、以下を確認する。なお、任意のメンターユーザで一回確認すればOK。
  • ダッシュボード画面の[Watch中]タブをクリック後、先ほど公開した質問をクリックし、AIによって生成された解答が「仮の解答です。」であること

Screenshot

※内部的なリファクタリングであり、見た目の変更はないためスクリーンショットはありません。

Summary by CodeRabbit

  • 新機能

    • 質問の作成・更新で通知イベントが発行され、メンターのウォッチ登録とAIによる回答生成が自動で起動します。
  • 改善

    • WIP状態の変化に応じて通知の発火条件が統一され、不要な処理発生が抑制されます。
  • リファクタ

    • 既存の購読設定を整理し、通知の購読先を再構成しました。

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 26, 2025

Walkthrough

QuestionsController が作成・更新時に ActiveSupport::Notifications を発火するように変更し、MentorsWatchForQuestionCreatorAIAnswerCreatorcall シグネチャを通知コールバック(5引数)に合わせ、購読登録を Newspaper 初期化子から ActiveSupport 初期化子へ移行しました。

Changes

Cohort / File(s) 変更概要
Controller
app/controllers/questions_controller.rb
createActiveSupport::Notifications.instrument 'question.create' を追加。updatesaved_change_to_wip? 判定時に ActiveSupport::Notifications.instrument 'question.update' を発火(既存の Newspaper.publish 呼び出しの条件付き実行を調整)。
Listeners / Models
app/models/ai_answer_creator.rb, app/models/mentors_watch_for_question_creator.rb
両クラスの call メソッド署名を def call(payload) から def call(_name, _started, _finished, _unique_id, payload) に変更(内部ロジックは変更なし)。
ActiveSupport subscriptions
config/initializers/active_support_notifications.rb
MentorsWatchForQuestionCreatorAIAnswerCreatorquestion.create / question.update に購読登録するエントリを追加。
Newspaper subscriptions removed
config/initializers/newspaper.rb
MentorsWatchForQuestionCreatorAIAnswerCreator の Newspaper 購読登録を削除。

Sequence Diagram(s)

sequenceDiagram
    participant Controller as QuestionsController
    participant ASNoti as ActiveSupport::Notifications
    participant MWQ as MentorsWatchForQuestionCreator
    participant AIA as AIAnswerCreator
    rect rgb(230,245,255)
    Controller->>ASNoti: instrument 'question.create' (payload: question)
    ASNoti->>MWQ: call(_name,_started,_finished,_unique_id,payload)
    ASNoti->>AIA: call(_name,_started,_finished,_unique_id,payload)
    end
    rect rgb(245,255,230)
    Controller->>ASNoti: instrument 'question.update' (payload: question) [if saved_change_to_wip?]
    ASNoti->>MWQ: call(_name,_started,_finished,_unique_id,payload)
    ASNoti->>AIA: call(_name,_started,_finished,_unique_id,payload)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–30分

Possibly related issues

Possibly related PRs

Suggested reviewers

  • matuaya
  • komagata

Poem

🐇
問いを運ぶ鈴の音、ぴょん、
森の耳に届く小さな合図。
リスナたちがそっと返す声、
コードの草原で踊る通知。

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 RuboCop (1.76.1)
app/models/ai_answer_creator.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:162:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:82:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

config/initializers/active_support_notifications.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:162:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:82:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

app/controllers/questions_controller.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:162:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:82:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

  • 1 others

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/convert-mentorwatch-and-aianswer-to-notifications

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sjabcdefin sjabcdefin self-assigned this Jul 26, 2025
@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 26, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee8a4bc and b5c7071.

📒 Files selected for processing (5)
  • app/controllers/questions_controller.rb (2 hunks)
  • app/models/ai_answer_creator.rb (1 hunks)
  • app/models/mentors_watch_for_question_creator.rb (1 hunks)
  • config/initializers/active_support_notifications.rb (1 hunks)
  • config/initializers/newspaper.rb (0 hunks)
💤 Files with no reviewable changes (1)
  • config/initializers/newspaper.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • config/initializers/active_support_notifications.rb
  • app/models/ai_answer_creator.rb
  • app/models/mentors_watch_for_question_creator.rb
  • app/controllers/questions_controller.rb
🧠 Learnings (2)
📓 Common learnings
Learnt from: reckyy
PR: fjordllc/bootcamp#8356
File: app/models/notification.rb:45-47
Timestamp: 2025-07-15T12:58:02.735Z
Learning: fjordllc/bootcampプロジェクトの`app/models/notification.rb`において、`training_completed`通知種別は`TARGETS_TO_KINDS`ハッシュに含めない設計になっている。これは`training_completed`が通知一覧のフィルタ対象ではないためで、意図的な設計判断である。
Learnt from: jun-kondo
PR: fjordllc/bootcamp#0
File: :0-0
Timestamp: 2025-07-23T21:11:21.826Z
Learning: fjordllc/bootcampプロジェクトの`app/models/inquiry_notifier.rb`において、`NOTIFICATION_SENDER_NAME`で指定される管理者ユーザーは必ず存在することがシステムの前提条件として保証されているため、senderがnilになることは想定されておらず、エラーハンドリングは不要である。
app/controllers/questions_controller.rb (1)

Learnt from: jun-kondo
PR: #8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトのapp/controllers/reports_controller.rbにおいて、createupdateアクションは両方とも@report.save_uniquelyを使用し、同じ:report_saveイベントと'report.save'イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (4)
config/initializers/active_support_notifications.rb (1)

20-23: ActiveSupport::Notificationsへの移行が適切に実装されています

MentorsWatchForQuestionCreatorとAIAnswerCreatorの両方がquestion.createquestion.updateイベントに正しく購読されており、既存の他の通知システムと一貫したパターンに従っています。

app/models/ai_answer_creator.rb (1)

4-4: ActiveSupport::Notifications対応のメソッドシグネチャが正しく更新されています

未使用パラメータに適切にアンダースコア接頭辞が付けられており、既存のロジックに影響を与えずにActiveSupport::Notificationsのコールバック仕様に準拠しています。

app/models/mentors_watch_for_question_creator.rb (1)

4-4: AIAnswerCreatorと一貫したメソッドシグネチャの更新

ActiveSupport::Notificationsのコールバック仕様に準拠した適切なメソッドシグネチャの更新が行われており、AIAnswerCreatorと一貫性が保たれています。

app/controllers/questions_controller.rb (1)

71-73: updateアクションの条件付きイベント発火ロジックは適切です

wip_changedフラグに基づいて両方のイベントシステム(ActiveSupport::NotificationsとNewspaper)で一貫してイベントが発火されており、移行期間中の整合性が保たれています。

ただし、変数名の抽出により可読性が向上していることを評価します。

@fjordllc fjordllc deleted a comment from coderabbitai bot Jul 26, 2025
@sjabcdefin sjabcdefin marked this pull request as ready for review July 26, 2025 05:10
@github-actions github-actions bot requested a review from komagata July 26, 2025 05:10
@sjabcdefin sjabcdefin requested review from matuaya and removed request for komagata July 26, 2025 05:12
@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@matuaya
お疲れ様です!
お手すきの際に、こちらのPRをレビューいただけると嬉しいです。😊🙏
ご負担になりそうでしたら、遠慮なく教えていただければと思います!😌👌

@matuaya
Copy link
Copy Markdown
Contributor

matuaya commented Jul 27, 2025

@sjabcdefin
返信遅れて大変申し訳ありません🙇‍♀️
レビュー承りました😊

Copy link
Copy Markdown
Contributor

@matuaya matuaya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確認しました!
Approveさせていただきます😊

@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@matuaya
お疲れさまです。😌
レビューありがとうございました!😌🙏

@sjabcdefin sjabcdefin requested a review from komagata July 29, 2025 08:57
@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@komagata
お疲れさまです。😌
お手すきの際に、レビューをお願いいたします。😌🙏

@komagata
Copy link
Copy Markdown
Member

@sjabcdefin conflictの修正をお願いします。

@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch from b5c7071 to dec8cd2 Compare July 29, 2025 22:57
@sjabcdefin
Copy link
Copy Markdown
Contributor Author

sjabcdefin commented Jul 29, 2025

@komagata
失礼いたしました。
conflict 修正いたしました。🙇‍♀️

すみません。まだ、CodeRabbit レビュー中でした。
終わりましたら、ご連絡いたします。

@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@komagata
Conflict 修正いたしました。
CodeRabbit レビューも完了しています。

よろしくお願いいたします。🙇‍♀️

Comment on lines +71 to +73
wip_changed = @question.saved_change_to_wip?
ActiveSupport::Notifications.instrument('question.update', { question: @question }) if wip_changed
Newspaper.publish(:question_update, { question: @question }) if wip_changed
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

後置のifは1行の時の書き方なのでこの場合は下記でいいかもです。

Suggested change
wip_changed = @question.saved_change_to_wip?
ActiveSupport::Notifications.instrument('question.update', { question: @question }) if wip_changed
Newspaper.publish(:question_update, { question: @question }) if wip_changed
if @question.saved_change_to_wip?
ActiveSupport::Notifications.instrument('question.update', { question: @question })
Newspaper.publish(:question_update, { question: @question })
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

後置ifを通常のif文に修正いたしました。
コミット:005b9e8

※rebaseの影響で該当のレビューコメントが表示されなくなっているようなので、こちらに返信させていただきました。🙇‍♀️

Comment on lines +24 to +27
ActiveSupport::Notifications.subscribe('question.create', MentorsWatchForQuestionCreator.new)
ActiveSupport::Notifications.subscribe('question.update', MentorsWatchForQuestionCreator.new)
ActiveSupport::Notifications.subscribe('question.create', AIAnswerCreator.new)
ActiveSupport::Notifications.subscribe('question.update', AIAnswerCreator.new)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同じインスタンスが複数作成されることになっちゃうので下記のような感じがいいかもです。

Suggested change
ActiveSupport::Notifications.subscribe('question.create', MentorsWatchForQuestionCreator.new)
ActiveSupport::Notifications.subscribe('question.update', MentorsWatchForQuestionCreator.new)
ActiveSupport::Notifications.subscribe('question.create', AIAnswerCreator.new)
ActiveSupport::Notifications.subscribe('question.update', AIAnswerCreator.new)
mentors_watch_for_question_creator = MentorsWatchForQuestionCreator.new
ActiveSupport::Notifications.subscribe('question.create', mentors_watch_for_question_creator)
ActiveSupport::Notifications.subscribe('question.update', mentors_watch_for_question_creator)
ai_answer_creator = AIAnswerCreator.new
ActiveSupport::Notifications.subscribe('question.create', ai_answer_creator)
ActiveSupport::Notifications.subscribe('question.update', ai_answer_creator)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同一インスタンスを1回だけ作成するように修正いたしました。
コミット:7a08275

※rebaseの影響で該当のレビューコメントが表示されなくなっているようなので、こちらに返信させていただきました。🙇‍♀️

@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch from dec8cd2 to 7a08275 Compare July 30, 2025 23:11
@komagata
Copy link
Copy Markdown
Member

komagata commented Aug 2, 2025

@sjabcdefin conflictの修正をお願いします。

@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch from 7a08275 to b5b530c Compare August 3, 2025 07:24
@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch 3 times, most recently from e375235 to 0ce5cd7 Compare August 15, 2025 06:38
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
app/models/mentors_watch_for_question_creator.rb (3)

8-10: insert_all の重複挿入対策(ユニーク制約 + unique_by/upsert)の検討を

イベントが何らかの理由で二重発火した場合や並行実行が起きた場合、重複 Watch が挿入され得ます(既存実装でも同様の懸念はありますが、通知方式移行に伴う呼び出しタイミングの違いで顕在化しやすくなる可能性があります)。

  • DB に (watchable_type, watchable_id, user_id) のユニークインデックスがあるかご確認ください。
  • ある場合は insert_all の unique_by を指定、もしくは upsert_all の利用をご検討ください(PostgreSQL 前提)。

例(ユニークインデックスがある場合):

Watch.insert_all(watch_question_records, unique_by: :index_watches_on_watchable_and_user) # 例: インデックス名

もしくは migration 例(未作成の場合):

add_index :watches, [:watchable_type, :watchable_id, :user_id], unique: true, name: :index_watches_on_watchable_and_user

12-22: メモリ使用の削減: mentor の id だけを pluck してレコード生成しませんか

User.mentor を AR オブジェクトで全件ロードせず、id だけを pluck すると軽量になります。

適用例:

-  def watch_records(question)
-    User.mentor.map do |mentor|
+  def watch_records(question)
+    User.mentor.pluck(:id).map do |mentor_id|
       {
         watchable_type: 'Question',
         watchable_id: question.id,
         created_at: Time.current,
         updated_at: Time.current,
-        user_id: mentor.id
+        user_id: mentor_id
       }
     end
   end

12-22: 公開インターフェースの最小化: watch_records を private に

外部から呼ばれる必要がなければ private 指定が妥当です。クラスの責務と公開APIが明確になります。

   def call(_name, _started, _finished, _unique_id, payload)
     question = payload[:question]
     return if question.wip? || question.watched?

     watch_question_records = watch_records(question)
     Watch.insert_all(watch_question_records) # rubocop:disable Rails/SkipsModelValidations
   end

-  def watch_records(question)
+  private
+
+  def watch_records(question)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e375235 and 0ce5cd7.

📒 Files selected for processing (5)
  • app/controllers/questions_controller.rb (2 hunks)
  • app/models/ai_answer_creator.rb (1 hunks)
  • app/models/mentors_watch_for_question_creator.rb (1 hunks)
  • config/initializers/active_support_notifications.rb (1 hunks)
  • config/initializers/newspaper.rb (0 hunks)
💤 Files with no reviewable changes (1)
  • config/initializers/newspaper.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/controllers/questions_controller.rb
  • app/models/ai_answer_creator.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • config/initializers/active_support_notifications.rb
  • app/models/mentors_watch_for_question_creator.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (3)
config/initializers/active_support_notifications.rb (2)

45-51: 単一インスタンスを使い回して購読する形に修正されていて良いです

Komagataさんの指摘どおり、同一インスタンスを複数イベントへ購読登録しており、不要なインスタンス生成が避けられています。実装方針・書き方ともにこのファイル内の既存パターンとも整合しています。


45-51: to_prepare 内での subscribe は開発環境のリロードで多重購読が積み上がらないか確認を

本ファイル全体で採用している既存パターンですが、development でコードがリロードされるたびに subscribe が追加される可能性があります。今回の question.create/update の購読でも同じ挙動になるため、開発環境で質問作成/公開変更時にリスナーの call が複数回実行されていないか、軽く確認をお願いします。

app/models/mentors_watch_for_question_creator.rb (1)

4-5: ActiveSupport::Notifications の5引数シグネチャ対応はOK

call(_name, _started, _finished, _unique_id, payload) に揃っており、未使用引数はプレフィックスで抑制されていて適切です。

@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@komagata
conflict 修正いたしました。
ご確認お願いいたします。😌🙏

@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch from 0ce5cd7 to 5a82425 Compare August 19, 2025 22:30
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
app/models/mentors_watch_for_question_creator.rb (3)

12-22: 同一トランザクション内での timestamp を揃えると微妙な差分が出にくいです

Time.current をループ内で都度呼ぶとミリ秒レベルで差が出ます。1回だけ取得して使い回すと見た目も監査上の扱いも安定します。

以下の小さなリファクタを検討ください。

   def watch_records(question)
-    User.mentor.map do |mentor|
+    now = Time.current
+    User.mentor.map do |mentor|
       {
         watchable_type: 'Question',
         watchable_id: question.id,
-        created_at: Time.current,
-        updated_at: Time.current,
+        created_at: now,
+        updated_at: now,
         user_id: mentor.id
       }
     end
   end

10-13: ヘルパーメソッドは private にして意図を明確化

watch_records は外部公開不要なので private 化しておくと API 面の誤用が減ります。

   end
-
-  def watch_records(question)
+  private
+
+  def watch_records(question)

8-10: ユニークインデックスを活かした upsert_all へのリファクタリング提案

watches テーブルにはすでに以下のユニークインデックスが存在するため、insert_all のままでは重複レコードが発生する可能性があります。Rails 6.1+ かつユニークインデックスありの前提で、upsert_all に置き換え、空配列時のガードを追加すると安全です。

・該当箇所

  • ファイル: app/models/mentors_watch_for_question_creator.rb
  • 行: 8–10
-    watch_question_records = watch_records(question)
-    Watch.insert_all(watch_question_records) # rubocop:disable Rails/SkipsModelValidations
+    watch_question_records = watch_records(question)
+    return if watch_question_records.empty?
+    # ユニークキー: (watchable_type, watchable_id, user_id)
+    Watch.upsert_all(
+      watch_question_records,
+      unique_by: %i[watchable_type watchable_id user_id]
+    ) # rubocop:disable Rails/SkipsModelValidations

– db/migrate/20230419022757_add_index_to_watches.rb にて以下が定義済みです

add_index :watches, [:watchable_type, :watchable_id, :user_id], unique: true
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce5cd7 and 5a82425.

📒 Files selected for processing (3)
  • app/controllers/questions_controller.rb (2 hunks)
  • app/models/ai_answer_creator.rb (1 hunks)
  • app/models/mentors_watch_for_question_creator.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/controllers/questions_controller.rb
  • app/models/ai_answer_creator.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/models/mentors_watch_for_question_creator.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (2)
app/models/mentors_watch_for_question_creator.rb (2)

4-4: ActiveSupport::Notifications 用の5引数シグネチャへの移行、問題なし

AS::Notifications の購読コールバックと整合しており、未使用引数にプレフィックスを付けている点も良いです。


5-6: payload[:question] は常に Question インスタンスですので、追加ガードは不要です
QuestionsController の instrument('question.create', { question: @question })instrument('question.update', { question: @question }) 呼び出しでは常に question: キーに @question(Question オブジェクト)が含まれており、本サブスクライバもこれらのイベントのみを対象としているため、payload[:question] が nil になるケースや型が異なるケースは想定されません。現状のままで問題ありません。

@komagata
Copy link
Copy Markdown
Member

@sjabcdefin 再度すみませんが、conflictの修正をお願いします。

…sに移行

- MentorsWatchForQuestionCreatorクラスをActiveSupport::Notificationsのインターフェースに対応
- QuestionsControllerのイベント発火にActiveSupport::Notifications.instrumentを追加
- active_support_notifications.rbにMentorsWatchForQuestionCreatorの登録を追加
- newspaper.rbからMentorsWatchForQuestionCreatorの登録を削除
- AIAnswerCreatorクラスをActiveSupport::Notificationsのインターフェースに対応
- active_support_notifications.rbにAIAnswerCreatorの登録を追加
- newspaper.rbからAIAnswerCreatorの登録を削除
複数行になる場合は通常のif文で書くの望ましいため
同じインスタンスを複数作成する必要がないため
@sjabcdefin sjabcdefin force-pushed the chore/convert-mentorwatch-and-aianswer-to-notifications branch from 5a82425 to 7e178f0 Compare August 25, 2025 10:25
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
app/models/mentors_watch_for_question_creator.rb (4)

8-10: 重複耐性と並行性を強化するため insert_allupsert_all(unique_by:) を推奨

通知は再送され得るため、同一レコードの多重挿入を DB 側で自然に弾けるようにしておくと堅牢です。upsert_allunique_by: %i[user_id watchable_type watchable_id] を指定すれば、ユニーク制約に基づく冪等性を確保できます(PostgreSQL 前提)。Rails/DB バージョン適合も合わせてご確認ください。

-    watch_question_records = watch_records(question)
-    Watch.insert_all(watch_question_records) # rubocop:disable Rails/SkipsModelValidations
+    watch_question_records = watch_records(question)
+    # 同一 (user_id, watchable_type, watchable_id) の多重挿入を防ぐ
+    Watch.upsert_all(
+      watch_question_records,
+      unique_by: %i[user_id watchable_type watchable_id]
+    ) # rubocop:disable Rails/SkipsModelValidations

12-22: Time.current を一度だけ評価して使い回し(微最適化+タイムスタンプの一貫性)

大量挿入時に Time.current を複数回呼ぶ必要はなく、1回だけ取得して全レコードに適用すると僅かながら効率的で、タイムスタンプのばらつきも避けられます。

-  def watch_records(question)
-    User.mentor.map do |mentor|
+  def watch_records(question)
+    now = Time.current
+    User.mentor.map do |mentor|
       {
         watchable_type: 'Question',
         watchable_id: question.id,
-        created_at: Time.current,
-        updated_at: Time.current,
+        created_at: now,
+        updated_at: now,
         user_id: mentor.id
       }
     end
   end

3-23: 配置の見直し(listener/subscriber としての役割を明確化)

本クラスは ActiveRecord モデルではなくイベントリスナーの責務です。app/models 直下よりも app/listeners(または app/subscribers)等へ配置すると役割が明確になり、Fat Model 回避・責務分離の観点でもガイドラインに沿います。既に同系の AIAnswerCreator があるなら、それと同一ポリシーの配置に揃えると可読性が上がります。


5-7: Watchable#watched? は全ユーザーの watch 存在を判定しており、メンターごとのチェックには不向きです

現状の question.watched?(app/models/concerns/watchable.rb:12–14)は

def watched?
  watches.present?
end

として定義されており、任意のユーザーによる watch が 1 件でもあれば true を返します。そのため、以下のリスナーで早期 return が走ると、既に別ユーザーが watch しているだけで他のメンター向けの watch 作成処理が丸ごとスキップされてしまいます。

対象箇所

  • app/models/concerns/watchable.rb 12–14 行目
  • app/models/mentors_watch_for_question_creator.rb 5–7 行目

―――

提案する対応例(メンター単位の漏れ分のみ作成)

  1. Watchable モジュールに次のようなメソッドを追加
    # app/models/concerns/watchable.rb
    def watched_by?(user)
      watches.exists?(user: user)
    end
  2. リスナーを以下のように書き換え
    # app/models/mentors_watch_for_question_creator.rb
    return if question.wip?
    
    question.mentors.each do |mentor|
      next if question.watched_by?(mentor)
      question.watches.create!(user: mentor)
    end

これにより、一部のメンターだけ watch が抜けているケースを正しく補完できます。

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a82425 and 7e178f0.

📒 Files selected for processing (5)
  • app/controllers/questions_controller.rb (2 hunks)
  • app/models/ai_answer_creator.rb (1 hunks)
  • app/models/mentors_watch_for_question_creator.rb (1 hunks)
  • config/initializers/active_support_notifications.rb (1 hunks)
  • config/initializers/newspaper.rb (0 hunks)
💤 Files with no reviewable changes (1)
  • config/initializers/newspaper.rb
🚧 Files skipped from review as they are similar to previous changes (3)
  • config/initializers/active_support_notifications.rb
  • app/controllers/questions_controller.rb
  • app/models/ai_answer_creator.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/models/mentors_watch_for_question_creator.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (1)
app/models/mentors_watch_for_question_creator.rb (1)

4-4: ActiveSupport::Notifications の5引数コールバックに適合 — OK

def call(_name, _started, _finished, _unique_id, payload) への変更は AS::Notifications の購読インターフェイスに準拠しており問題ありません。

@sjabcdefin
Copy link
Copy Markdown
Contributor Author

@komagata
お疲れさまです。😌
conflict 修正いたしました。
ご確認お願いいたします。😌🙏

Copy link
Copy Markdown
Member

@komagata komagata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確認させて頂きました。OKです〜🙆‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants