Skip to content

E2614 Role Based Reviewing#347

Open
veera2508 wants to merge 11 commits intoexpertiza:mainfrom
veera2508:fp
Open

E2614 Role Based Reviewing#347
veera2508 wants to merge 11 commits intoexpertiza:mainfrom
veera2508:fp

Conversation

@veera2508
Copy link
Copy Markdown

@veera2508 veera2508 commented Apr 29, 2026

Summary by CodeRabbit

  • New Features

    • Role-based review for assignments with duty details and per-duty member limits visible in assignment data
    • Endpoints to update duty member limits and to set/clear a participant's duty
    • Participant responses now include duty name and ID
  • Bug Fixes

    • Enforced duty assignment ownership and team-level duty limits with proper validation
  • Tests

    • Added request specs covering duty limits, updates, and related error cases

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@s-poorna has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 59 minutes and 53 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cd8e767-bb7a-40a1-b4da-6a7e49a4599f

📥 Commits

Reviewing files that changed from the base of the PR and between 389a220 and 8e605f0.

📒 Files selected for processing (2)
  • app/controllers/assignments_duties_controller.rb
  • app/controllers/teams_participants_controller.rb

Walkthrough

This PR adds role-based duty support: moves duty member limits from global duties to assignment-specific join records, adds duty associations and validations, new endpoints to update participant duties and assignment-duty limits, schema migrations, serializer updates, dependency tweaks, and new request specs.

Changes

Cohort / File(s) Summary
Dependencies & Config
Gemfile, config/application.rb, docker-compose.yml
Added redis and moved faker to top-level; bumped Rails defaults to 8.0; switched cache store to :redis_cache_store with keyword url; removed trailing ? from DATABASE_URL in docker-compose.
Database Migrations & Schema
db/migrate/20260423202332_adjust_duty_limits.rb, db/migrate/20260423210000_add_duty_id_to_assignment_questionnaires.rb, db/schema.rb
Moved max_members_for_duty from duties to assignments_duties (default 1); added duty_id to assignment_questionnaires; schema updated with additional index/index changes and other housekeeping.
Routing
config/routes.rb
Added member PATCH :limit route for assignments/:assignment_id/duties/:id/limit and PATCH :duty member route for participants; constrained authorization segment for participant authorization route.
Controllers
app/controllers/assignments_duties_controller.rb, app/controllers/participants_controller.rb, app/controllers/teams_participants_controller.rb
Added update_limit action with before_action loaders and authorization; refactored index/create/destroy to operate on join records; added ParticipantsController#update_duty; TeamsParticipantsController#update_duty now validates duty belongs to assignment and persists participant.duty_id.
Models
app/models/assignment_questionnaire.rb, app/models/assignments_duty.rb, app/models/participant.rb
Added optional belongs_to :duty to AssignmentQuestionnaire and Participant; validated max_members_for_duty numericality (>=1) on AssignmentsDuty; Participant adds team-scoped duty limit validation enforcing per-assignment limit.
Serializers
app/serializers/assignment_serializer.rb, app/serializers/participant_serializer.rb
AssignmentSerializer gains has_role_based_review and assignment_duties (duty_id, duty_name, max_members_for_duty); ParticipantSerializer exposes duty_id and duty_name.
Tests / Specs
spec/requests/api/v1/assignments_duties_controller_spec.rb, spec/requests/api/v1/participants_controller_spec.rb, spec/requests/api/v1/teams_participants_controller_spec.rb
Added comprehensive request specs for assignments duties API and participant duty update; adjusted teams_participants spec to assert duty persisted on participant.
Other files
setup.sh, .gitignore
Minor edits: commented optional destructive db drop lines in setup.sh; added ignores for setup.sh, Gemfile.lock, and Gemfile to .gitignore.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as AssignmentsDutiesController
    participant AuthZ as Authorization
    participant Loader as BeforeAction Loader
    participant Model as AssignmentsDuty Model
    participant DB as Database

    Client->>Controller: PATCH /assignments/:a_id/duties/:id/limit
    Controller->>AuthZ: action_allowed!
    AuthZ-->>Controller: allow / deny
    Controller->>Loader: set_assignment
    Loader->>DB: fetch Assignment
    DB-->>Loader: Assignment or nil
    Loader-->>Controller: `@assignment` or 404
    Controller->>Loader: set_assignment_duty
    Loader->>DB: fetch AssignmentsDuty by assignment_id & id
    DB-->>Loader: AssignmentsDuty or nil
    Loader-->>Controller: `@assignment_duty` or 404
    Controller->>Model: assign max_members_for_duty, validate & save
    Model->>DB: persist
    alt saved
        DB-->>Model: success
        Controller-->>Client: 200 + serialized duty info
    else validation fails
        DB-->>Model: errors
        Controller-->>Client: 422 + errors
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Poem

🐇 I hopped through migrations, moved limits with care,
Duties now local, counted per assignment fair,
A sprinkle of Redis, faker joins the tea,
Controllers and specs sing in harmony!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'E2614 Role Based Reviewing' is partially related to the changeset, referring to role-based duties and assignments, but does not capture the main implementation details such as Redis integration, gem reorganization, or the specific technical changes across controllers and models.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
docker-compose.yml (1)

21-23: ⚠️ Potential issue | 🟠 Major

Move hard-coded credentials to environment variables.

The docker-compose.yml file hard-codes MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD at lines 22-23, and the app service includes the plaintext password in DATABASE_URL. While these are development credentials (documented in the README), this pattern should still be changed to use environment variables to prevent accidental exposure of real credentials if the setup is repurposed or forked.

Suggested fix
   db:
     image: mysql:8.0
     restart: always
     environment:
       MYSQL_USER: ${MYSQL_USER:-dev}
-      MYSQL_PASSWORD: expertiza
-      MYSQL_ROOT_PASSWORD: expertiza
+      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
+      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}

Create a .env file at the repository root (add .env to .gitignore):

MYSQL_USER=dev
MYSQL_PASSWORD=expertiza
MYSQL_ROOT_PASSWORD=expertiza

Also update the app service DATABASE_URL to use environment variables.

Note: The CI/CD workflows in .github/workflows/ use the same hard-coded credentials for test environments, which is acceptable for ephemeral test containers. However, .env should still be excluded from version control for local development to follow security best practices.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 21 - 23, Replace the hard-coded DB creds in
docker-compose.yml by reading MYSQL_USER, MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD
from environment variables and update the app service's DATABASE_URL to
interpolate those env vars (replace literal values for MYSQL_USER /
MYSQL_PASSWORD / MYSQL_ROOT_PASSWORD and the plaintext password inside
DATABASE_URL); add instructions to create a local .env (and ensure .env is
listed in .gitignore) with keys MYSQL_USER, MYSQL_PASSWORD and
MYSQL_ROOT_PASSWORD so developers can set dev credentials without committing
them.
spec/requests/api/v1/teams_participants_controller_spec.rb (1)

160-182: ⚠️ Potential issue | 🟠 Major

Create the duty mapping in the 200-path setup.

update_duty now rejects duty IDs that are not present in assignments_duties, but this example still posts a hard-coded duty_id: 2 without creating the Duty/AssignmentsDuty row. That makes the spec fail with 422, or worse, pass only if some unrelated test data happens to create ID 2.

Make the example self-contained
       response(200, 'duty updated successfully') do
+        let!(:duty) do
+          Duty.create!(name: 'Leader', instructor: instructor)
+        end
+        let!(:assignment_duty) do
+          AssignmentsDuty.create!(assignment: assignment, duty: duty, max_members_for_duty: 2)
+        end
         let!(:participant_for_update) do
           AssignmentParticipant.create!(
             parent_id: assignment.id,
             user:      student_user,
             handle:    student_user.name
           )
         end
         let(:teams_participant) do
           TeamsParticipant.create!(
             team_id:        team_with_assignment.id,
             participant_id: participant_for_update.id,
             user_id:        participant_for_update.user_id
           )
         end
         let(:token)         { JsonWebToken.encode({ id: student_user.id }) }
         let(:Authorization) { "Bearer #{token}" }
-        let(:payload)       { { teams_participant_id: teams_participant.id, teams_participant: { duty_id: 2 } } }
+        let(:payload)       { { teams_participant_id: teams_participant.id, teams_participant: { duty_id: duty.id } } }

         run_test! do |response|
           json = JSON.parse(response.body)
           expect(json['message']).to eq("Duty updated successfully")
-          expect(participant_for_update.reload.duty_id).to eq(2)
+          expect(participant_for_update.reload.duty_id).to eq(duty.id)
         end
       end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/requests/api/v1/teams_participants_controller_spec.rb` around lines 160
- 182, The spec posts a hard-coded duty_id (2) but doesn't create the
corresponding Duty/AssignmentsDuty, causing a 422; in the 200-path setup create
a Duty (e.g., via Duty.create!) and then an AssignmentsDuty (or
AssignmentsDuties) linking that Duty to the existing assignment (use the
existing assignment variable), and change the payload to use that duty's id
(teams_participant: { duty_id: duty.id } or set let(:payload) to reference the
created duty) so the update_duty validation passes; locate the setup around
AssignmentParticipant/TeamsParticipant (participant_for_update,
teams_participant, payload) and add the Duty and AssignmentsDuty creation there.
app/models/participant.rb (1)

13-30: ⚠️ Potential issue | 🟠 Major

Scope this duty-limit validation to assignment participants.

For CourseParticipant, parent_id is a course id, so AssignmentsDuty.find_by(assignment_id: parent_id, ...) misses and the || 1 fallback starts enforcing a one-member duty cap on course teams. This should only run when the participant actually belongs to an assignment.

Minimal fix
-  validate :duty_limit_within_team, if: -> { duty_id.present? && team.present? }
+  validate :duty_limit_within_team, if: -> { duty_id.present? && team.present? && assignment.present? }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/participant.rb` around lines 13 - 30, The duty_limit_within_team
validation is running for CourseParticipant too and uses parent_id as an
assignment id; restrict it so it only runs for assignment participants—either
add the guard to the validator (e.g. change the validation predicate to include
a check that this is an Assignment participant or that parent_type ==
'Assignment' / instance_of? AssignmentParticipant) or add an early return in
duty_limit_within_team (return unless self is an assignment participant) before
calling AssignmentsDuty.find_by; reference duty_limit_within_team,
CourseParticipant and AssignmentParticipant (or parent_type) when making the
change.
config/application.rb (1)

20-35: ⚠️ Potential issue | 🟠 Major

Split the Rails 8 defaults and Redis cache configuration out of this feature PR.

config.load_defaults 8.0 changes framework-wide behavior and may introduce unintended breaking changes across the application. The Redis cache store configuration is similarly orthogonal to the duty/role feature—neither the Duty model, AssignmentsDuty, nor RolesController use Rails.cache, and the configured cache store is not utilized anywhere in the codebase. Bundling these changes with a narrowly-scoped feature makes regressions much harder to isolate and complicates reviewing this PR's actual impact.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/application.rb` around lines 20 - 35, Remove the unrelated
framework/defaults and global cache changes from this feature PR: revert the
addition of config.load_defaults 8.0 and the config.cache_store =
:redis_cache_store, { url: ENV['CACHE_STORE'], expires_in: 3.days } entry in
application.rb so the feature only contains duty/role-specific changes; leave
any API-only setting (config.api_only) intact if it was preexisting, and move
the Rails 8 defaults and Redis cache configuration into a separate follow-up PR
or config change commit after the feature is merged.
🧹 Nitpick comments (3)
docker-compose.yml (2)

13-16: Consider DB/Redis readiness (healthchecks) to avoid startup races.

depends_on is present (Lines 13-16), but there are no healthchecks. Without readiness gating, the app can attempt connections before MySQL/Redis are ready, causing flaky startup in local/dev environments.

Add healthchecks for db and redis and conditionally gate app startup on healthy state where supported by your Compose version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 13 - 16, Add container healthchecks for the
services named "db" and "redis" and update the "app" service's depends_on to
wait for service health where supported; specifically, add a healthcheck block
for db (use a mysql-ready check such as running "mysqladmin ping" or an
equivalent probe that returns success when MySQL is accepting connections) and a
healthcheck block for redis (use "redis-cli ping" or equivalent), and change the
"app" service depends_on entry to use service_healthy/condition: service_healthy
so Compose will wait for those healthchecks to pass before starting the app;
adjust timeouts/retries to reasonable values to avoid false negatives.

31-31: Pin redis image version instead of redis:latest (repro/security).

Using redis:latest (Line 31) can introduce unexpected behavior changes and makes deployments less reproducible. Prefer a fixed tag (e.g., redis:7.2.x) consistent with your target compatibility.

Check what Redis version the app expects (if any) and whether other compose/services already rely on a specific Redis major/minor version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` at line 31, The docker-compose.yml currently uses an
unpinned Redis image ("image: redis:latest"); change that to a specific, tested
tag (e.g., "redis:7.2.x" or the exact patch like "redis:7.2.5") to ensure
reproducible deployments and security; update the image line under the Redis
service and commit. Before choosing the tag, search the codebase and configs
(environment variables, connection settings, Dockerfiles, CI, and any redis
client usage) for expected Redis features or version constraints and check other
compose/services for already used Redis major/minor versions to pick a
compatible fixed tag.
app/controllers/assignments_duties_controller.rb (1)

59-71: Extract this duty payload shape into one shared serializer/presenter.

The same { duty_id, duty_name, max_members_for_duty } mapping now exists here and in app/serializers/assignment_serializer.rb. Keeping it in one place will make future response changes less error-prone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/assignments_duties_controller.rb` around lines 59 - 71,
Extract the duplicate duty payload ({ duty_id, duty_name, max_members_for_duty
}) into a single reusable serializer/presenter class (e.g.,
AssignmentDutyPresenter or AssignmentDutySerializer) and replace the inline
mapping in serialized_assignment_duties/serialize_assignment_duty with a call to
this new presenter; update app/serializers/assignment_serializer.rb to use the
same presenter so both places delegate to the new class (expose a method like
as_json or to_hash) and remove the duplicated mapping from the controller.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/teams_participants_controller.rb`:
- Around line 29-35: The duty lookup currently uses participant.parent_id which
is incorrect for team participants; instead, first ensure the participant
belongs to an assignment team (gate the action to assignment teams) by checking
team_participant.team is an assignment-type team, then derive the assignment
from team_participant.team (e.g. assignment = team_participant.team.parent or
team_participant.team.assignment) and use that assignment.id in
AssignmentsDuty.find_by(assignment_id: assignment.id, duty_id: duty_id); if the
team is not an assignment team, render the appropriate error response.

In `@app/serializers/assignment_serializer.rb`:
- Around line 4-15: The serializer methods has_role_based_review and
assignment_duties are causing extra DB queries by calling
object.assignments_duties and accessing assignment_duty.duty for each serialized
Assignment; fix it by preloading assignments_duties and their duty association
in the controller (e.g. include :assignments_duties and assignments_duties:
:duty) and change AssignmentSerializer to accept the already-loaded association
data (use object.association(:assignments_duties).loaded? or accept a
precomputed payload) so has_role_based_review checks the in-memory collection
and assignment_duties iterates the preloaded objects without triggering queries.

In `@db/migrate/20260423202332_adjust_duty_limits.rb`:
- Around line 2-5: The migration currently adds
assignments_duties.max_members_for_duty with default 1 and then drops
duties.max_members_for_duty, losing existing per-duty limits; change it to a
safe, reversible migration by: add the new column first
(assignments_duties.max_members_for_duty), backfill values from
duties.max_members_for_duty into assignments_duties via a SQL UPDATE or
ActiveRecord join using the duties and assignments_duties tables, then only
after backfill remove the original duties.max_members_for_duty; implement this
in an explicit up/down or reversible migrate method in adjust_duty_limits.rb and
reference the table names (:duties, :assignments_duties) and column symbol
(:max_members_for_duty) when making the changes.

In `@db/migrate/20260423210000_add_duty_id_to_assignment_questionnaires.rb`:
- Around line 2-4: The migration currently uses add_column
:assignment_questionnaires, :duty_id, :integer which creates an unindexed,
non-foreign-key integer column and mismatches other duty_id columns; replace
this with add_reference :assignment_questionnaires, :duty, type: :bigint, index:
true, foreign_key: true (and set null: false if the association is required) so
the DB gets a bigint-typed FK plus an index and constraint; update any
tests/fixtures if they relied on the old integer column.

In `@db/schema.rb`:
- Line 13: The schema.rb is missing the migration that adds duty_id to
assignment_questionnaires (migration
20260423210000_add_duty_id_to_assignment_questionnaires.rb), so update the
schema by running the pending migration(s) and regenerating the schema dump: run
the migration(s) (e.g., rails db:migrate) in your development environment (or
run the specific migration file), then run rails db:schema:dump (or rails
db:structure:dump if your project uses structure) to produce an updated
db/schema.rb that includes the assignment_questionnaires.duty_id column so the
model’s belongs_to :duty matches the schema.

In `@docker-compose.yml`:
- Line 7: The DATABASE_URL in docker-compose.yml is missing the trailing '?'
expected by your config/database.yml URL rewriting (the .gsub('?',
'_development?') / .gsub('?', '_test?') / .gsub('?', '_production?') logic);
update the DATABASE_URL value to include a trailing '?' (so the value ends with
'?') so the .gsub replacements produce distinct environment-specific database
names and restore test isolation.

---

Outside diff comments:
In `@app/models/participant.rb`:
- Around line 13-30: The duty_limit_within_team validation is running for
CourseParticipant too and uses parent_id as an assignment id; restrict it so it
only runs for assignment participants—either add the guard to the validator
(e.g. change the validation predicate to include a check that this is an
Assignment participant or that parent_type == 'Assignment' / instance_of?
AssignmentParticipant) or add an early return in duty_limit_within_team (return
unless self is an assignment participant) before calling
AssignmentsDuty.find_by; reference duty_limit_within_team, CourseParticipant and
AssignmentParticipant (or parent_type) when making the change.

In `@config/application.rb`:
- Around line 20-35: Remove the unrelated framework/defaults and global cache
changes from this feature PR: revert the addition of config.load_defaults 8.0
and the config.cache_store = :redis_cache_store, { url: ENV['CACHE_STORE'],
expires_in: 3.days } entry in application.rb so the feature only contains
duty/role-specific changes; leave any API-only setting (config.api_only) intact
if it was preexisting, and move the Rails 8 defaults and Redis cache
configuration into a separate follow-up PR or config change commit after the
feature is merged.

In `@docker-compose.yml`:
- Around line 21-23: Replace the hard-coded DB creds in docker-compose.yml by
reading MYSQL_USER, MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD from environment
variables and update the app service's DATABASE_URL to interpolate those env
vars (replace literal values for MYSQL_USER / MYSQL_PASSWORD /
MYSQL_ROOT_PASSWORD and the plaintext password inside DATABASE_URL); add
instructions to create a local .env (and ensure .env is listed in .gitignore)
with keys MYSQL_USER, MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD so developers can
set dev credentials without committing them.

In `@spec/requests/api/v1/teams_participants_controller_spec.rb`:
- Around line 160-182: The spec posts a hard-coded duty_id (2) but doesn't
create the corresponding Duty/AssignmentsDuty, causing a 422; in the 200-path
setup create a Duty (e.g., via Duty.create!) and then an AssignmentsDuty (or
AssignmentsDuties) linking that Duty to the existing assignment (use the
existing assignment variable), and change the payload to use that duty's id
(teams_participant: { duty_id: duty.id } or set let(:payload) to reference the
created duty) so the update_duty validation passes; locate the setup around
AssignmentParticipant/TeamsParticipant (participant_for_update,
teams_participant, payload) and add the Duty and AssignmentsDuty creation there.

---

Nitpick comments:
In `@app/controllers/assignments_duties_controller.rb`:
- Around line 59-71: Extract the duplicate duty payload ({ duty_id, duty_name,
max_members_for_duty }) into a single reusable serializer/presenter class (e.g.,
AssignmentDutyPresenter or AssignmentDutySerializer) and replace the inline
mapping in serialized_assignment_duties/serialize_assignment_duty with a call to
this new presenter; update app/serializers/assignment_serializer.rb to use the
same presenter so both places delegate to the new class (expose a method like
as_json or to_hash) and remove the duplicated mapping from the controller.

In `@docker-compose.yml`:
- Around line 13-16: Add container healthchecks for the services named "db" and
"redis" and update the "app" service's depends_on to wait for service health
where supported; specifically, add a healthcheck block for db (use a mysql-ready
check such as running "mysqladmin ping" or an equivalent probe that returns
success when MySQL is accepting connections) and a healthcheck block for redis
(use "redis-cli ping" or equivalent), and change the "app" service depends_on
entry to use service_healthy/condition: service_healthy so Compose will wait for
those healthchecks to pass before starting the app; adjust timeouts/retries to
reasonable values to avoid false negatives.
- Line 31: The docker-compose.yml currently uses an unpinned Redis image
("image: redis:latest"); change that to a specific, tested tag (e.g.,
"redis:7.2.x" or the exact patch like "redis:7.2.5") to ensure reproducible
deployments and security; update the image line under the Redis service and
commit. Before choosing the tag, search the codebase and configs (environment
variables, connection settings, Dockerfiles, CI, and any redis client usage) for
expected Redis features or version constraints and check other compose/services
for already used Redis major/minor versions to pick a compatible fixed tag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8836a2d7-5a18-4aa6-ab83-99bd573c1390

📥 Commits

Reviewing files that changed from the base of the PR and between cc03ecd and df699ac.

📒 Files selected for processing (15)
  • Gemfile
  • app/controllers/assignments_duties_controller.rb
  • app/controllers/teams_participants_controller.rb
  • app/models/assignment_questionnaire.rb
  • app/models/assignments_duty.rb
  • app/models/participant.rb
  • app/serializers/assignment_serializer.rb
  • config/application.rb
  • config/routes.rb
  • db/migrate/20260423202332_adjust_duty_limits.rb
  • db/migrate/20260423210000_add_duty_id_to_assignment_questionnaires.rb
  • db/schema.rb
  • docker-compose.yml
  • spec/requests/api/v1/assignments_duties_controller_spec.rb
  • spec/requests/api/v1/teams_participants_controller_spec.rb

Comment thread app/controllers/teams_participants_controller.rb Outdated
Comment on lines +4 to +15
def has_role_based_review
object.assignments_duties.exists?
end

def assignment_duties
object.assignments_duties.includes(:duty).map do |assignment_duty|
{
duty_id: assignment_duty.duty_id,
duty_name: assignment_duty.duty&.name,
max_members_for_duty: assignment_duty.max_members_for_duty
}
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect where AssignmentSerializer is used and whether those paths preload duties.
# Expected result: collection render paths should preload assignments_duties: :duty.

echo "== Assignment render sites =="
rg -nP --type ruby 'AssignmentSerializer|each_serializer:\s*AssignmentSerializer|serializer:\s*AssignmentSerializer|render\s+json:\s+@assignments|render\s+json:\s+@assignment' app/controllers app/serializers

echo
echo "== Duty preloading in controllers =="
rg -nP --type ruby 'includes\([^)]*assignments_duties[^)]*duty|preload\([^)]*assignments_duties[^)]*duty|eager_load\([^)]*assignments_duties[^)]*duty' app/controllers

Repository: expertiza/reimplementation-back-end

Length of output: 756


🏁 Script executed:

#!/bin/bash
# Check for other potential uses of AssignmentSerializer or assignment rendering
echo "== All AssignmentSerializer references =="
rg -n 'AssignmentSerializer' --type ruby

echo
echo "== Render json with assignment variable (broader search) =="
rg -nB2 -A2 'render.*json.*assignment' --type ruby app/controllers

echo
echo "== Check for respond_with patterns =="
rg -n 'respond_with.*assignment' --type ruby app/controllers

echo
echo "== Check what student.assignment returns =="
rg -n 'def assignment' --type ruby app/models/user.rb app/models/student.rb

Repository: expertiza/reimplementation-back-end

Length of output: 13995


🏁 Script executed:

#!/bin/bash
# Check if Student model exists or if it's part of User
echo "== Student model or relationship =="
fd -e rb . app/models | xargs grep -l "class.*Student" | head -5

echo
echo "== Check AssignmentSerializer attributes =="
cat -n app/serializers/assignment_serializer.rb | head -30

echo
echo "== Check if assignment_duties is accessed in controllers =="
rg -n 'assignment_duties|assignments_duties' app/controllers/student_teams_controller.rb

Repository: expertiza/reimplementation-back-end

Length of output: 962


🏁 Script executed:

#!/bin/bash
# Verify the Assignment model associations
echo "== Assignment model class definition and associations =="
cat -n app/models/assignment.rb | head -50

echo
echo "== Check for has_many :assignments_duties in Assignment =="
rg -A5 'has_many.*assignments_duties|has_many.*:assignments_duties' app/models/assignment.rb

Repository: expertiza/reimplementation-back-end

Length of output: 3536


These serializer methods trigger unnecessary database queries per assignment render.

Both has_role_based_review and assignment_duties issue separate database queries. Since AssignmentSerializer is currently used in student_teams_controller.rb to serialize single assignments, each render causes 2 extra queries that could be avoided by preloading assignments_duties and assignments_duties.duty in the controller and passing the data directly to the serializer.

🧰 Tools
🪛 RuboCop (1.86.1)

[convention] 4-4: Use 2 (not 5) spaces for indentation.

(Layout/IndentationWidth)


[convention] 5-5: Use 2 (not 5) spaces for indentation.

(Layout/IndentationWidth)


[convention] 8-8: Use 2 (not 5) spaces for indentation.

(Layout/IndentationWidth)


[convention] 9-9: Use 2 (not 5) spaces for indentation.

(Layout/IndentationWidth)


[convention] 10-10: Use 2 (not 5) spaces for indentation.

(Layout/IndentationWidth)


[convention] 11-11: Use 2 spaces for indentation in a hash, relative to the start of the line where the left curly brace is.

(Layout/FirstHashElementIndentation)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/serializers/assignment_serializer.rb` around lines 4 - 15, The serializer
methods has_role_based_review and assignment_duties are causing extra DB queries
by calling object.assignments_duties and accessing assignment_duty.duty for each
serialized Assignment; fix it by preloading assignments_duties and their duty
association in the controller (e.g. include :assignments_duties and
assignments_duties: :duty) and change AssignmentSerializer to accept the
already-loaded association data (use
object.association(:assignments_duties).loaded? or accept a precomputed payload)
so has_role_based_review checks the in-memory collection and assignment_duties
iterates the preloaded objects without triggering queries.

Comment on lines +2 to +5
def change
remove_column :duties, :max_members_for_duty, :integer
add_column :assignments_duties, :max_members_for_duty, :integer, default: 1
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Backfill existing limits before dropping the source column.

This migration resets every existing duty limit to 1. Any non-default duties.max_members_for_duty values are lost on migrate, and change is misleading here because the split becomes irreversible once per-assignment values diverge.

Safer migration outline
 class AdjustDutyLimits < ActiveRecord::Migration[8.0]
-  def change
-    remove_column :duties, :max_members_for_duty, :integer
-    add_column :assignments_duties, :max_members_for_duty, :integer, default: 1
+  def up
+    add_column :assignments_duties, :max_members_for_duty, :integer, default: 1, null: false
+
+    execute <<~SQL
+      UPDATE assignments_duties ad
+      INNER JOIN duties d ON d.id = ad.duty_id
+      SET ad.max_members_for_duty = COALESCE(d.max_members_for_duty, 1)
+    SQL
+
+    remove_column :duties, :max_members_for_duty, :integer
+  end
+
+  def down
+    raise ActiveRecord::IrreversibleMigration, 'duty limits become assignment-specific after this migration'
   end
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@db/migrate/20260423202332_adjust_duty_limits.rb` around lines 2 - 5, The
migration currently adds assignments_duties.max_members_for_duty with default 1
and then drops duties.max_members_for_duty, losing existing per-duty limits;
change it to a safe, reversible migration by: add the new column first
(assignments_duties.max_members_for_duty), backfill values from
duties.max_members_for_duty into assignments_duties via a SQL UPDATE or
ActiveRecord join using the duties and assignments_duties tables, then only
after backfill remove the original duties.max_members_for_duty; implement this
in an explicit up/down or reversible migrate method in adjust_duty_limits.rb and
reference the table names (:duties, :assignments_duties) and column symbol
(:max_members_for_duty) when making the changes.

Comment on lines +2 to +4
def change
add_column :assignment_questionnaires, :duty_id, :integer
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how assignment_questionnaires.duty_id is declared and whether schema includes an index/FK.
rg -n 'add_(column|reference).*assignment_questionnaires.*duty' db/migrate
rg -n 'create_table "assignment_questionnaires"|t\.(integer|bigint) "duty_id"|t\.index \["duty_id"\]|add_foreign_key "assignment_questionnaires", "duties"' db/schema.rb

Repository: expertiza/reimplementation-back-end

Length of output: 571


🏁 Script executed:

#!/bin/bash
# Check for other migrations that might add the index
rg -n 'add_index.*assignment_questionnaires.*duty|duty_id' db/migrate/*.rb | head -20

# Check if the migration file matches what's shown - read full content
echo "=== Full migration file content ==="
cat db/migrate/20260423210000_add_duty_id_to_assignment_questionnaires.rb

# Search for foreign key constraint in schema or migrations
echo "=== Foreign key search ==="
rg -n 'add_foreign_key.*assignment_questionnaires|foreign_key.*duty' db/

Repository: expertiza/reimplementation-back-end

Length of output: 848


🏁 Script executed:

#!/bin/bash
# Check the AssignmentQuestionnaire model
echo "=== AssignmentQuestionnaire model ==="
find app -name "*assignment_questionnaire*" -type f | head -5

# Search for the model definition
rg -n 'class AssignmentQuestionnaire' app/

# Check for migrations AFTER 20260423210000 that touch assignment_questionnaires
echo "=== Migrations after 20260423210000 ==="
ls -1 db/migrate/*.rb | grep -A 10 '20260423210000' | head -20

# Check if there are any later migrations
rg -n 'assignment_questionnaires' db/migrate/*.rb | grep -v '20260423210000'

Repository: expertiza/reimplementation-back-end

Length of output: 789


🏁 Script executed:

#!/bin/bash
# Read the AssignmentQuestionnaire model
echo "=== AssignmentQuestionnaire model content ==="
cat app/models/assignment_questionnaire.rb

# Get full schema entry for assignment_questionnaires including foreign keys
echo "=== Schema for assignment_questionnaires (full) ==="
rg -A 30 'create_table "assignment_questionnaires"' db/schema.rb

Repository: expertiza/reimplementation-back-end

Length of output: 1580


🏁 Script executed:

#!/bin/bash
# Verify duties table exists
echo "=== Duties table in schema ==="
rg -A 5 'create_table "duties"' db/schema.rb | head -15

# Check all duty_id column types in schema for consistency
echo "=== All duty_id columns in schema ==="
rg 'bigint "duty_id"|integer "duty_id"' db/schema.rb

Repository: expertiza/reimplementation-back-end

Length of output: 516


Use add_reference instead of add_column to create the foreign key and index.

This adds duty_id as a bare integer column without an index or foreign key constraint, allowing orphaned records and degrading lookup/filter performance on this association. Additionally, other duty_id columns in the schema use :bigint, but this migration uses :integer (type mismatch).

Suggested migration fix
 class AddDutyIdToAssignmentQuestionnaires < ActiveRecord::Migration[8.0]
   def change
-    add_column :assignment_questionnaires, :duty_id, :integer
+    add_reference :assignment_questionnaires, :duty, null: true, foreign_key: true, index: true
   end
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@db/migrate/20260423210000_add_duty_id_to_assignment_questionnaires.rb` around
lines 2 - 4, The migration currently uses add_column :assignment_questionnaires,
:duty_id, :integer which creates an unindexed, non-foreign-key integer column
and mismatches other duty_id columns; replace this with add_reference
:assignment_questionnaires, :duty, type: :bigint, index: true, foreign_key: true
(and set null: false if the association is required) so the DB gets a
bigint-typed FK plus an index and constraint; update any tests/fixtures if they
relied on the old integer column.

Comment thread db/schema.rb Outdated
Comment thread docker-compose.yml
environment:
RAILS_ENV: development
DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation?
DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find database.yml file
find . -name "database.yml" -o -name "database.yml.*" | head -20

Repository: expertiza/reimplementation-back-end

Length of output: 127


🏁 Script executed:

# Also check for any database configuration files
fd -e yml -e yaml | grep -i database

Repository: expertiza/reimplementation-back-end

Length of output: 99


🏁 Script executed:

# Read the database.yml file to check for the gsub pattern
cat -n ./config/database.yml

Repository: expertiza/reimplementation-back-end

Length of output: 617


🏁 Script executed:

# Also check if there's a .old version with different content
cat -n ./config/database.yml.old

Repository: expertiza/reimplementation-back-end

Length of output: 2381


🏁 Script executed:

# Verify the exact state of docker-compose.yml to see the full context around line 7
cat -n ./docker-compose.yml

Repository: expertiza/reimplementation-back-end

Length of output: 1124


Add trailing ? to DATABASE_URL in docker-compose.yml.

The current DATABASE_URL (line 7) is missing the trailing ? required by your config/database.yml URL rewriting logic. Lines 10, 14, and 18 of database.yml use .gsub('?', '_development?'), .gsub('?', '_test?'), and .gsub('?', '_production?') to differentiate environment-specific databases. Without the ?, all three environments will connect to the same database (reimplementation), breaking test isolation.

Suggested fix
-      DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation
+      DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation?
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation
DATABASE_URL: mysql2://root:expertiza@db:3306/reimplementation?
🧰 Tools
🪛 Checkov (3.2.525)

[medium] 7-8: Basic Auth Credentials

(CKV_SECRET_4)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` at line 7, The DATABASE_URL in docker-compose.yml is
missing the trailing '?' expected by your config/database.yml URL rewriting (the
.gsub('?', '_development?') / .gsub('?', '_test?') / .gsub('?', '_production?')
logic); update the DATABASE_URL value to include a trailing '?' (so the value
ends with '?') so the .gsub replacements produce distinct environment-specific
database names and restore test isolation.

@github-actions
Copy link
Copy Markdown

3 Warnings
⚠️ Pull request has duplicated commit messages.
⚠️ Schema changes detected without a corresponding DB migration.
⚠️ Pull request modifies config or setup files: setup.sh.

Generated by 🚫 Danger

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/participants_controller.rb`:
- Around line 103-115: The update_duty action incorrectly returns HTTP 201
(:created) for a successful update; change the render in update_duty (method
update_duty in ParticipantsController) to use status: :ok to match REST
conventions and other controllers (UsersController#update,
RolesController#update, DutiesController#update); update the success render call
from render json: participant, status: :created to render json: participant,
status: :ok and run related controller specs to ensure expected status
assertions are updated.

In `@spec/requests/api/v1/participants_controller_spec.rb`:
- Line 360: The failing spec creates a Duty without the required instructor
reference; update the `let!(:role_duty)` declaration to supply an instructor
(e.g. create or use the spec's existing instructor fixture) by calling
`Duty.create!(name: 'Developer', instructor: instructor)` or `Duty.create!(name:
'Developer', instructor_id: instructor.id)` so the `Duty.create!` call (assigned
to `role_duty`) satisfies the non-null `instructor_id` constraint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 973d6461-7eba-4ce1-97e8-1a9f7436e74a

📥 Commits

Reviewing files that changed from the base of the PR and between df699ac and 389a220.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .gitignore
  • app/controllers/participants_controller.rb
  • app/serializers/participant_serializer.rb
  • config/routes.rb
  • db/schema.rb
  • setup.sh
  • spec/requests/api/v1/participants_controller_spec.rb
✅ Files skipped from review due to trivial changes (2)
  • .gitignore
  • setup.sh

Comment on lines +103 to +115
# Update the specified participant duty
# PATCH /participants/:id/duty
def update_duty
participant = find_participant
return unless participant

participant.duty_id = params[:duty_id].presence
if participant.save
render json: participant, status: :created
else
render json: participant.errors, status: :unprocessable_entity
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use :ok instead of :created for update operations.

The update_duty action returns HTTP 201 (:created) on line 111, but this endpoint updates an existing participant resource. Per REST conventions and codebase patterns (see UsersController#update, RolesController#update, DutiesController#update which all return :ok), successful updates should return HTTP 200.

Proposed fix
     if participant.save
-      render json: participant, status: :created
+      render json: participant, status: :ok
     else
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/participants_controller.rb` around lines 103 - 115, The
update_duty action incorrectly returns HTTP 201 (:created) for a successful
update; change the render in update_duty (method update_duty in
ParticipantsController) to use status: :ok to match REST conventions and other
controllers (UsersController#update, RolesController#update,
DutiesController#update); update the success render call from render json:
participant, status: :created to render json: participant, status: :ok and run
related controller specs to ensure expected status assertions are updated.

}
}

let!(:role_duty) { Duty.create!(name: 'Developer') }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing required instructor_id when creating Duty.

The duties table has instructor_id defined as null: false (see db/schema.rb line 181). This Duty.create! call will raise an ActiveRecord::NotNullViolation because instructor_id is not provided.

🐛 Proposed fix
-      let!(:role_duty) { Duty.create!(name: 'Developer') }
+      let!(:role_duty) { Duty.create!(name: 'Developer', instructor_id: instructor.id) }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let!(:role_duty) { Duty.create!(name: 'Developer') }
let!(:role_duty) { Duty.create!(name: 'Developer', instructor_id: instructor.id) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/requests/api/v1/participants_controller_spec.rb` at line 360, The
failing spec creates a Duty without the required instructor reference; update
the `let!(:role_duty)` declaration to supply an instructor (e.g. create or use
the spec's existing instructor fixture) by calling `Duty.create!(name:
'Developer', instructor: instructor)` or `Duty.create!(name: 'Developer',
instructor_id: instructor.id)` so the `Duty.create!` call (assigned to
`role_duty`) satisfies the non-null `instructor_id` constraint.

head :no_content
end

# PATCH /assignments/:assignment_id/duties/:id/limit
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.

Please add a comment saying that the limit is the max # of team members who can have this duty.

team_participant.update(duty_id: duty_id)
render json: { message: "Duty updated successfully" }, status: :ok
participant = team_participant.participant

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.

Needs a clearer comment.


def action_allowed!
unless current_user_has_instructor_privileges?
render json: { error: 'Not authorized' }, status: :forbidden
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

action_allowed! renders 403 but does not return, so the action may continue after unauthorized access.

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.

6 participants