Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix filtering on new saved annotation page #5838

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

jorg-vr
Copy link
Contributor

@jorg-vr jorg-vr commented Oct 1, 2024

This pull request fixes the filtering of annotations on the 'new saved annotations' page.

This issue was a quite complex rails pluralization issue.

Problem was joins(:submission) creates INNER JOIN submissions ON submissions.id = annotations.submission_id while filters like where(submission: { exercise_id: exercise_id }) create INNER JOIN submissions AS submission ON submission.id = annotations.submission_id.

If both the joins and the where clause are present, the table name in the query is submission singular. If only the join clause is present the table name is submissions plural.

Starting to use 'submission' singular in most places and avoiding explicitly naming the table in pluck clauses resolves the issues.

  • Tests were added

@jorg-vr jorg-vr added the bug Something isn't working label Oct 1, 2024
@jorg-vr jorg-vr self-assigned this Oct 1, 2024
@jorg-vr jorg-vr requested a review from bmesuere as a code owner October 1, 2024 12:36
Copy link

coderabbitai bot commented Oct 1, 2024

Warning

Rate limit exceeded

@jorg-vr has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 53 seconds before requesting another review.

⌛ 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.

📥 Commits

Files that changed from the base of the PR and between 40633be and 2e997f8.

Walkthrough

The pull request introduces changes to the SavedAnnotationsController and AnnotationPolicy. The new action in the controller now retrieves course_id and exercise_id directly from submission records associated with @annotations, optimizing the data retrieval process. The show action has been enhanced to support JavaScript responses via AJAX. In the policy, the resolve method has been updated to correctly filter annotations based on user roles and submissions. Additionally, the test suite for the controller has been expanded to include new test cases for filtering annotations by staff and zeus users.

Changes

Files Change Summary
app/controllers/saved_annotations_controller.rb Optimized new action to retrieve course_id and exercise_id directly from submission; added format.js response block to show action.
app/policies/annotation_policy.rb Corrected hash key in resolve method to use submission instead of submissions; no changes to method signatures.
test/controllers/saved_annotation_controller_test.rb Added tests for staff and zeus users to verify filtering of existing annotations on the new saved annotation page.

Possibly related PRs

Suggested labels

feature


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • 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.

Copy link

@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

🧹 Outside diff range and nitpick comments (4)
app/policies/annotation_policy.rb (1)

7-7: Approved: Efficient query addressing the pluralization issue.

The changes effectively resolve the filtering issue mentioned in the PR objectives. By using scope.released.where(submission: { user: user }), you've eliminated the need for an explicit join and consistently used the singular submission, addressing the pluralization problem.

The addition of .or(scope.where(submission: { course_id: user.administrating_courses.map(&:id) })) ensures that users can access annotations from courses they administer, which is a good improvement in functionality.

Consider caching the result of user.administrating_courses.map(&:id) if it's used frequently, as it could potentially be an expensive operation for users administering many courses:

admin_course_ids = user.administrating_courses.pluck(:id)
scope.released.where(submission: { user: user }).or(scope.where(submission: { course_id: admin_course_ids }))

This change would reduce the number of database queries if the method is called multiple times.

app/controllers/saved_annotations_controller.rb (3)

45-46: Approved: Good fix for the pluralization issue.

The changes effectively address the pluralization issue mentioned in the PR objectives. Using an explicit INNER JOIN with the singular table name 'submission' ensures consistency between the join and where clauses, resolving the filtering problem.

For improved readability, consider extracting the join condition into a separate method or constant:

SUBMISSION_JOIN = 'INNER JOIN submissions AS submission ON submission.id = annotations.submission_id'

@courses = Course.where(id: @annotations.joins(SUBMISSION_JOIN).pluck('submission.course_id').uniq)
@exercises = Activity.where(id: @annotations.joins(SUBMISSION_JOIN).pluck('submission.exercise_id').uniq)

This would make the queries more concise and easier to maintain.


Line range hint 1-108: Summary: Effective fix with room for further optimization

The changes in this PR successfully address the filtering issue on the new saved annotation page by consistently using singular table names in joins and where clauses. The addition of the format.js response in the show action enhances the controller's flexibility.

Given that you're open to suggestions for further improvements, consider the following:

  1. Implement the suggested refactoring for the join condition in the new action to improve readability.
  2. Review the query performance, especially for the @annotations, @courses, and @exercises assignments in the new action. Consider using database-level joins and selects to optimize these queries if they become performance bottlenecks.
  3. Ensure comprehensive test coverage for both the filtering fix and the new AJAX functionality.
  4. Consider implementing eager loading (e.g., includes or preload) for associated models if they're frequently accessed in views to reduce N+1 query issues.

If you're looking for more substantial improvements:

  1. Evaluate if some of the query logic can be moved to the model layer to keep the controller lighter.
  2. Consider implementing a service object for complex annotation filtering if the logic continues to grow in complexity.
  3. Explore using ActiveRecord's merge method for combining scopes, which might provide a cleaner way to handle complex queries.

These suggestions aim to further enhance the code's maintainability and performance. Let me know if you'd like more detailed advice on any of these points.


Missing Integration Tests for AJAX Functionality

No existing integration tests were found for the format.js response in the show action of SavedAnnotationsController. It's recommended to add tests to ensure the new AJAX functionality behaves as expected.

🔗 Analysis chain

Line range hint 28-32: Approved: Enhanced response handling with format.js

The addition of the format.js block in the show action improves the controller's capability to handle AJAX requests, allowing for more dynamic interactions on the client side.

To ensure the new AJAX functionality works as expected:

  1. Verify that there's a corresponding JavaScript view (show.js.erb or similar) to handle the AJAX response.
  2. Test the AJAX functionality in the browser to confirm it behaves correctly.
  3. Consider adding integration tests to cover this new AJAX path.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for the existence of a JavaScript view for the show action
fd -e js.erb -e js show app/views/saved_annotations

Length of output: 93


Script:

#!/bin/bash
# Search for integration tests for the show action handling format.js in saved_annotations_controller
rg 'describe .*SavedAnnotationsController' tests/
rg 'describe "GET #show" .*format: :js' tests/

Length of output: 194


Script:

#!/bin/bash
# Search for tests related to SavedAnnotationsController's show action handling format.js
rg 'describe .*SavedAnnotationsController' .
rg 'describe "GET #show" .*format: :js' .

Length of output: 90

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4e54faf and 8bc4623.

📒 Files selected for processing (3)
  • app/controllers/saved_annotations_controller.rb (1 hunks)
  • app/policies/annotation_policy.rb (1 hunks)
  • test/controllers/saved_annotation_controller_test.rb (1 hunks)
🧰 Additional context used
📓 Learnings (3)
📓 Common learnings
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.
app/controllers/saved_annotations_controller.rb (1)
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.
test/controllers/saved_annotation_controller_test.rb (1)
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.
🔇 Additional comments (3)
app/policies/annotation_policy.rb (2)

Line range hint 26-30: Verify consistency of record.course usage

While the resolve method now uses submission consistently, other methods like show? and anonymous? still use record.course. To maintain consistency and avoid potential issues:

  1. Verify that record.course is equivalent to record.submission.course in all cases.
  2. Consider updating these methods to use record.submission.course if appropriate, to align with the changes in the resolve method.

Run the following script to check the usage of record.course and record.submission.course:

This will help ensure consistency across the policy methods.

Also applies to: 57-61

✅ Verification successful

Consistency of record.course Usage Verified

All policy methods consistently use record.submission.course. No instances of record.course were found in app/policies/annotation_policy.rb or related policy files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of record.course and record.submission.course

# Test: Search for record.course usage
echo "Occurrences of record.course:"
rg --type ruby 'record\.course' app/policies/

# Test: Search for record.submission.course usage
echo "Occurrences of record.submission.course:"
rg --type ruby 'record\.submission\.course' app/policies/

Length of output: 788


Line range hint 1-61: Reminder: Ensure test coverage for policy changes

Based on a previous learning note, it's important to update the test suite when making changes to controller methods or policy logic. While this change is in a policy file rather than a controller, the principle still applies.

To ensure adequate test coverage:

  1. Verify that existing tests cover the new resolve method logic.
  2. Add new tests if necessary to cover different scenarios (e.g., regular users, admins, users with both their own submissions and administered courses).

Run the following script to check for related test files and their content:

This will help identify existing tests and areas where coverage might need to be improved.

test/controllers/saved_annotation_controller_test.rb (1)

Line range hint 1-72: Enhance test coverage for SQL query issue

While the new tests cover the filtering functionality, they don't directly address the complex pluralization issue with SQL queries mentioned in the PR objectives. To ensure comprehensive coverage, consider adding tests that specifically target this issue.

  1. Add a test that verifies the correct SQL query is generated when both joins and where clauses are present. This could be done by using to_sql on the query and asserting the correct table names are used.
  2. Include edge cases that might have been affected by the pluralization issue, such as queries with multiple joins or complex where conditions.

Here's a suggested additional test:

test 'should generate correct SQL query with singular table name' do
  exercise = create(:exercise)
  annotation = create(:annotation, submission: create(:submission, exercise: exercise))
  
  query = SavedAnnotation.joins(:submission).where(submission: { exercise_id: exercise.id })
  
  assert_includes query.to_sql, "INNER JOIN \"submission\"", "Should use singular 'submission' in JOIN clause"
  assert_includes query.to_sql, "WHERE \"submission\".\"exercise_id\" = #{exercise.id}", "Should use singular 'submission' in WHERE clause"
end

Additionally, ensure that the controller action being tested (new action in this case) is actually using the fixed query. If it's not, consider adding or updating a test for that action to verify it's using the correct query structure.

To confirm that the controller is using the correct query, we can run the following script:

This will help us verify that the controller is indeed using the singular table name in both the joins and where clauses.

Comment on lines +53 to +61
test 'staff should be able to filter existing annotations on new page' do
get new_saved_annotation_path

assert_response :success

get new_saved_annotation_path, params: { exercise_id: 1 }

assert_response :success
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage for staff filtering

The test correctly checks if staff can access the new saved annotation page with and without filtering. However, it could be improved to verify the actual filtering functionality.

Consider the following improvements:

  1. Add setup code to create test data (e.g., saved annotations with different exercise_ids).
  2. Verify the content of the response, not just the status code.
  3. Assert that the filtered results only contain annotations for the specified exercise_id.

Here's a suggested improvement:

test 'staff should be able to filter existing annotations on new page' do
  exercise1 = create(:exercise)
  exercise2 = create(:exercise)
  annotation1 = create(:saved_annotation, user: @user, course: @course, exercise: exercise1)
  annotation2 = create(:saved_annotation, user: @user, course: @course, exercise: exercise2)

  get new_saved_annotation_path
  assert_response :success
  assert_select 'your-selector-for-annotation', 2

  get new_saved_annotation_path, params: { exercise_id: exercise1.id }
  assert_response :success
  assert_select 'your-selector-for-annotation', 1
  assert_select 'your-selector-for-annotation-title', annotation1.title
end

Replace 'your-selector-for-annotation' with the actual CSS selector used in your view to represent annotations.

Comment on lines +63 to +72
test 'zeus should be able to filter existing annotations on new page' do
sign_in users(:zeus)
get new_saved_annotation_path

assert_response :success

get new_saved_annotation_path, params: { exercise_id: 1 }

assert_response :success
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor zeus test and enhance coverage

The zeus test is very similar to the staff test, which suggests an opportunity for refactoring to reduce code duplication. Additionally, it has the same limitations in terms of verifying the actual filtering functionality.

Consider the following improvements:

  1. Refactor the common testing logic into a shared method.
  2. Enhance the test to verify the content of the response, similar to the suggestions for the staff test.
  3. Ensure that zeus users can only see their own annotations in the filtered results.

Here's a suggested refactoring and improvement:

def assert_can_filter_annotations(user)
  sign_in user
  exercise1 = create(:exercise)
  exercise2 = create(:exercise)
  annotation1 = create(:saved_annotation, user: user, course: @course, exercise: exercise1)
  annotation2 = create(:saved_annotation, user: user, course: @course, exercise: exercise2)

  get new_saved_annotation_path
  assert_response :success
  assert_select 'your-selector-for-annotation', 2

  get new_saved_annotation_path, params: { exercise_id: exercise1.id }
  assert_response :success
  assert_select 'your-selector-for-annotation', 1
  assert_select 'your-selector-for-annotation-title', annotation1.title
end

test 'staff should be able to filter existing annotations on new page' do
  assert_can_filter_annotations(@user)
end

test 'zeus should be able to filter existing annotations on new page' do
  assert_can_filter_annotations(users(:zeus))
end

This refactoring reduces duplication and ensures consistent testing across user types. Adjust the selectors and assertions as needed to match your actual view structure.

@jorg-vr jorg-vr marked this pull request as draft October 1, 2024 14:47
@jorg-vr jorg-vr marked this pull request as ready for review October 8, 2024 13:16
Copy link

@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: 3

🧹 Outside diff range and nitpick comments (1)
app/policies/annotation_policy.rb (1)

6-6: Redundant .all call can be omitted

Calling .all on a relation is redundant since annotations is already an ActiveRecord::Relation. Omitting .all simplifies the code without changing its functionality.

Apply this diff to remove the redundancy:

-annotations.all
+annotations
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8bc4623 and fb3f620.

📒 Files selected for processing (2)
  • app/controllers/saved_annotations_controller.rb (1 hunks)
  • app/policies/annotation_policy.rb (1 hunks)
🧰 Additional context used
📓 Learnings (2)
📓 Common learnings
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.
app/controllers/saved_annotations_controller.rb (1)
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.
🔇 Additional comments (3)
app/controllers/saved_annotations_controller.rb (2)

Line range hint 1-114: Query: Where are the added tests located?

The PR objectives mention that tests were added to verify the functionality of the changes. However, these tests are not visible in the current file. Could you please provide the location of the added tests? This will help ensure that the changes are properly covered by the test suite.

To locate the relevant test files, you can run:

If you need any assistance in writing additional tests or improving the existing ones, please let me know.


45-46: Approved: Good fix for the filtering issue. Consider edge cases and alternatives.

The changes effectively address the table name inconsistency issue mentioned in the PR objectives. Using pluck directly on submission.course_id and submission.exercise_id avoids the need for an explicit join and consistently uses the singular table name, resolving the pluralization problem.

To ensure the changes don't introduce any unintended side effects, please verify the behavior in edge cases, such as:

  1. When there are no annotations
  2. When all annotations have the same course or exercise

You mentioned not being entirely satisfied with this fix. Are there any specific concerns you have about the current implementation? I'd be happy to help brainstorm alternative solutions if you have any ideas in mind.

app/policies/annotation_policy.rb (1)

10-10: Verify that annotations.none returns the correct empty relation

Ensure that annotations.none behaves as expected in this context. While none should return an empty relation, it's good to confirm that no unintended side effects occur.

You can test this by checking the result of annotations.none:

annotations.none.to_a # Should return an empty array

@@ -1,13 +1,13 @@
class AnnotationPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
annotations = scope.joins('INNER JOIN submissions AS submission ON submission.id = annotations.submission_id')
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor join to use ActiveRecord methods instead of raw SQL

Using raw SQL in joins reduces readability and may introduce SQL injection risks. Consider utilizing ActiveRecord's query interface to define the join with a table alias.

You can refactor the join as follows:

-annotations = scope.joins('INNER JOIN submissions AS submission ON submission.id = annotations.submission_id')
+annotations = scope.joins(:submission)

Since Annotation belongs to Submission, you can use the association to perform the join:

annotations = scope.joins(:submission)

If you need to reference the table with an alias due to pluralization issues, you can use joins with a string that's safer by leveraging sanitize_sql:

annotations = scope.joins(ActiveRecord::Base.sanitize_sql(['INNER JOIN submissions submission ON submission.id = annotations.submission_id']))

However, it's often better to adjust the query to avoid raw SQL altogether.

Comment on lines 4 to 10
annotations = scope.joins('INNER JOIN submissions AS submission ON submission.id = annotations.submission_id')
if user&.zeus?
scope.all
annotations.all
elsif user
common = scope.joins(:submission)
common.released.where(submissions: { user: user }).or(common.where(submissions: { course_id: user.administrating_courses.map(&:id) }))
annotations.released.where(submission: { user: user }).or(annotations.where(submission: { course_id: user.administrating_courses.map(&:id) }))
else
scope.none
annotations.none
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure test coverage for the updated resolve method

With changes to the resolve method, it's important to update the test suite to cover the new logic paths, especially since we've introduced a new variable and modified the query structure.

This aligns with previous learnings about updating tests when introducing new variables. Would you like assistance in writing additional tests to ensure comprehensive coverage?

elsif user
common = scope.joins(:submission)
common.released.where(submissions: { user: user }).or(common.where(submissions: { course_id: user.administrating_courses.map(&:id) }))
annotations.released.where(submission: { user: user }).or(annotations.where(submission: { course_id: user.administrating_courses.map(&:id) }))
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify complex where conditions for better readability

The where clause combines multiple conditions in a way that can be difficult to read and maintain. Breaking it into smaller scopes or using predicate methods can enhance clarity.

Consider refactoring as follows:

-annotations.released.where(submission: { user: user }).or(annotations.where(submission: { course_id: user.administrating_courses.map(&:id) }))
+user_annotations = annotations.released.where(submission: { user: user })
+admin_annotations = annotations.where(submission: { course_id: user.administrating_courses.ids })
+user_annotations.or(admin_annotations)

This separates the conditions and makes the logic more transparent.

📝 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
annotations.released.where(submission: { user: user }).or(annotations.where(submission: { course_id: user.administrating_courses.map(&:id) }))
user_annotations = annotations.released.where(submission: { user: user })
admin_annotations = annotations.where(submission: { course_id: user.administrating_courses.ids })
user_annotations.or(admin_annotations)

@jorg-vr
Copy link
Contributor Author

jorg-vr commented Oct 24, 2024

try to avoid pluck and no explicit sql

Copy link

@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

🧹 Outside diff range and nitpick comments (1)
app/controllers/saved_annotations_controller.rb (1)

Line range hint 1-108: Consider architectural improvements for better maintainability.

The controller is well-structured but could benefit from some architectural improvements:

  1. Consider extracting complex query logic into query objects:
# app/queries/saved_annotation_query.rb
class SavedAnnotationQuery
  def initialize(annotations)
    @annotations = annotations
  end

  def available_for_new_saved_annotation(current_user)
    @annotations
      .where(saved_annotation_id: nil)
      .where(user_id: current_user.id)
  end

  def courses_and_exercises
    submission_ids = @annotations.pluck(:submission_id).uniq
    submissions = Submission.where(id: submission_ids).pluck(:course_id, :exercise_id)
    course_ids, exercise_ids = submissions.transpose
    [
      Course.where(id: course_ids.uniq),
      Activity.where(id: exercise_ids.uniq)
    ]
  end
end
  1. Extract breadcrumb logic into a concern:
# app/controllers/concerns/breadcrumbable.rb
module Breadcrumbable
  extend ActiveSupport::Concern

  private

  def saved_annotations_crumbs(*additional_crumbs)
    [[I18n.t('saved_annotations.index.title'), saved_annotations_path]] + additional_crumbs
  end
end

These improvements would:

  • Make the code more maintainable and testable
  • Reduce duplication
  • Improve separation of concerns

Would you like me to provide more detailed examples or help implement these improvements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between fb3f620 and 40633be.

📒 Files selected for processing (2)
  • app/controllers/saved_annotations_controller.rb (1 hunks)
  • app/policies/annotation_policy.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/policies/annotation_policy.rb
🧰 Additional context used
📓 Learnings (1)
app/controllers/saved_annotations_controller.rb (1)
Learnt from: jorg-vr
PR: dodona-edu/dodona#5648
File: app/controllers/saved_annotations_controller.rb:88-89
Timestamp: 2024-07-29T12:25:56.607Z
Learning: When adding new instance variables in controller methods, ensure to update the test suite to cover these variables.

Comment on lines 45 to 47
submissions = Submission.where(id: @annotations.map(&:submission_id).uniq)
@courses = Course.where(id: submissions.map(&:course_id).uniq)
@exercises = Activity.where(id: submissions.map(&:exercise_id).uniq)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing the queries for better performance.

While the current implementation fixes the pluralization issue, it could be optimized for better performance. Consider these improvements:

-    submissions = Submission.where(id: @annotations.map(&:submission_id).uniq)
-    @courses = Course.where(id: submissions.map(&:course_id).uniq)
-    @exercises = Activity.where(id: submissions.map(&:exercise_id).uniq)
+    submission_ids = @annotations.pluck(:submission_id).uniq
+    submissions = Submission.where(id: submission_ids).pluck(:course_id, :exercise_id)
+    course_ids, exercise_ids = submissions.transpose
+    @courses = Course.where(id: course_ids.uniq)
+    @exercises = Activity.where(id: exercise_ids.uniq)

This optimization:

  1. Uses pluck instead of map to reduce memory usage
  2. Retrieves course_id and exercise_id in a single query
  3. Maintains the fix for the pluralization issue while improving performance

Alternative approach using eager loading:

@annotations = @annotations.includes(:submission)
submissions = @annotations.map(&:submission).compact
@courses = Course.where(id: submissions.map(&:course_id).uniq)
@exercises = Activity.where(id: submissions.map(&:exercise_id).uniq)

Choose the approach that best fits your needs:

  • First approach: Better performance, less memory usage
  • Second approach: More ActiveRecord-like, better for complex objects

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant