CSC/ECE 517 Fall 2024 - E2483. Reimplement Notification Controller and Model
Details about the Notification Model and Controller
The Notification model in Expertiza serves a critical role in enhancing communication and user engagement on the platform. It is essential because it automates the process of alerting users to significant events, such as receiving feedback, team changes, or assignment updates, without requiring users to actively seek out this information. By streamlining these updates, the model reduces the risk of missed information, helping users stay on top of their responsibilities, collaborate more effectively, and respond in a timely manner. This function is particularly valuable in a platform like Expertiza, where users juggle multiple assignments, peer reviews, and team interactions, and would otherwise need to manually monitor changes and updates or rely on external tools for reminders.
The Notification model also improves the overall user experience by organizing and presenting notifications in a way that makes them easy to access and track. It stores notification details (like type, content, and timestamp) and associates them with specific users, ensuring each user receives personalized, relevant updates. Users can view notifications sorted by date or filtered by read/unread status, making it easier to prioritize tasks and responses. Ultimately, the Notification model supports Expertiza’s usability by helping users stay informed and accountable, contributing to an environment that encourages active participation and smooth collaboration across assignments.
Objective
The objective was to create a Notification model in the backend to handle notifications for the Expertiza system.
This model will manage notifications for different courses and be accessible to students and instructors based on their association with a course. Notifications can have attributes such as subject, description, expiration date, and status (active/inactive).
The main objective is to create the Notification controller and model to handle sending and receiving notifications on the Expertiza platform.
Requirements
- Create a new Notification model with appropriate attributes and relationships:
- Define associations with User and Course models
- Implement scopes for filtering notifications based on roles
- Add validation rules for notification data
- Include timestamp fields for proper notification tracking
- Develop a NotificationsController with:
* Standard CRUD operations (create, read, update, delete) * Role-based access control logic * Course enrollment verification * Proper error handling and response formatting * RESTful API endpoints * Implement authorization logic: Add policies to ensure students only see relevant notifications
- Write comprehensive test cases:
* Unit tests for the Notification model * Controller tests covering all CRUD operations * Integration tests for role-based access * Test cases for error scenarios and edge cases
Design Goals
Implement the Notification backend: Implement the Notification in Ruby on Rails.
1. Model Creation:
Create a Notification model with the following key attributes:
- id: Primary key (auto-generated).
- subject: Brief title of the notification.
- description: Detailed message of the notification.
- expiration_date: The date when the notification should expire.
- is_active: Boolean flag to mark if the notification is active or inactive.
- is_unread: Boolean to mark if a notification is unread (default to true for new notifications).
2. Associations:
- Define associations between Notification, User, and Course models.
- User Association: Each notification should have a creator (e.g., a teaching assistant or instructor).
- Course Association: Each notification should be linked to a course, and the enrolled students should be able to view relevant notifications.
3. Relationships:
- One-to-Many: Course to Notification, as each course can have multiple notifications.
- One-to-Many: User (creator) to Notification, as a user can create multiple notifications.
- Many-to-Many (indirect): User to Notification via course enrollment, as multiple students can view a notification if they are enrolled in the course associated with the notification.
Schema
Associations
Notification
- belongs_to :user - Links the notification to its creator (TA, instructor).
- belongs_to :course - Links the notification to a course.
User
- has_many :notifications - Users (instructors, TAs) can create multiple notifications.
- has_many :viewable_notifications, through: :courses - Students can view notifications for courses they are enrolled in.
Course
- has_many :notifications - Each course can have multiple notifications.
- has_many :students, through: :enrollments - Defines which students are associated with the course.
UML Diagram
Relationship Explanation
- User to Notification: A User (e.g., TA, instructor) can create multiple notifications.
- Course to Notification: Each Course can have multiple notifications associated with it.
- User (Student) to Notification (Viewer): Through Course enrollment, students can access notifications related to their courses.
Implementation
NotificationController Implementation: Following are the controller methods:
1. create:
- Description: This method is responsible for creating a new notification. It validates the incoming data to ensure that all required fields are present and correctly formatted. Only authorized users (instructors or TAs) can create notifications. Upon successful creation, the method associates the notification with the specified course and user (creator).
- API Endpoint: POST /notifications
- Parameters:
- subject (required): The title of the notification that briefly describes its purpose (limited to 255 characters).
- description (optional): Detailed content of the notification.
- expiration_date (optional): The date until which the notification remains valid. Defaults to no expiration.
- is_active (optional): A boolean flag indicating if the notification is active (default is true).
- course_id (required): The course associated with the notification.
- Authorization: Ensures that only users with roles of TA or instructor can create a notification.
- Response:
- Success: Returns the newly created notification with a 201 status.
- Error: Returns validation errors (e.g., missing fields, invalid data) with a 422 status.
2. index:
- Description: The index method retrieves a list of notifications based on specific filters, such as active status, unread status, or course association. This endpoint ensures that students only see notifications related to the courses they are enrolled in. It supports querying with multiple optional parameters for flexibility.
- API Endpoint: GET /notifications
- Parameters: Query parameters (optional):
- is_active: Filter notifications by their active status.
- is_unread: Retrieve only unread notifications for the current user.
- course_id: Fetch notifications specific to a course.
- Authorization: Students can only access notifications for courses they are enrolled in. Instructors and TAs can view all notifications they have created.
- Response:
- Success: Returns an array of filtered notifications with a 200 status.
- Error: Returns a 403 status if unauthorized.
3. show:
- Description: This method retrieves the details of a specific notification identified by its unique ID. It ensures that the current user is authorized to view the notification (e.g., enrolled in the course or the creator of the notification).
- API Endpoint: GET /notifications/:id
- Parameters:
- id: The unique identifier of the notification to be retrieved.
- Authorization: Students can view notifications for courses they are enrolled in. Instructors and TAs can view notifications they have created.
- Response:
- Success: Returns the notification details with a 200 status.
- Error: Returns a 404 status if the notification is not found or the user is unauthorized.
4. update:
- Description: This method allows the creator of a notification (TA or instructor) to update its details. It validates the incoming data and ensures that the user is authorized to make changes.
- API Endpoint: PUT /notifications/:id
- Parameters: Fields that can be updated include subject, description, expiration_date, and is_active.
- Authorization: Only the creator of the notification can update it.
- Response:
- Success: Returns the updated notification with a 200 status.
- Error: Returns a 403 status if the user does not have the required permissions.
5. destroy:
- Description: This method deletes a specific notification. Only the user who created the notification can perform this action. The method ensures that no unauthorized users can delete notifications.
- API Endpoint: DELETE /notifications/:id
- Parameters:
- id: The unique identifier of the notification to be deleted.
- Authorization: Only the creator of the notification can delete it.
- Response:
- Success: Returns a 204 status with no content upon successful deletion.
- Error: Returns a 403 status if the user is unauthorized.
6. toggle_notification_visibility:
- Description: This method toggles the is_active status of a notification. For example, a TA or instructor can deactivate a notification that is no longer relevant. The method ensures that only the creator of the notification can perform this action.
- API Endpoint: PATCH /notifications/:id/toggle
- Parameters:
- id: The unique identifier of the notification to be toggled.
- Authorization: Only the creator of the notification can toggle its visibility.
- Response:
- Success: Returns the updated notification status with a 200 status.
- Error: Returns a 403 status if unauthorized.
Model Implementation:
1. Notification model:
- Description: The Notification model represents a single notification. It includes all attributes, validations, and associations necessary for managing notifications.
- Attributes:
- subject: The title of the notification (required, max length: 255).
- description: The detailed content of the notification.
- expiration_date: The date when the notification should expire (validated to be in the future).
- is_active: Indicates whether the notification is active (default: true).
- is_unread: Marks if a notification is unread (default: true).
- course_id: Links the notification to a course.
- user_id: Links the notification to its creator.
- Associations:
- belongs_to :course
- belongs_to :user
- Validations:
- Ensures the presence of subject, course_id, and user_id.
- Validates expiration_date to ensure it is not in the past.
- Scopes:
- active: Filters notifications that are active.
- unread: Filters unread notifications for a user.
2. User Model:
- Associations:
- has_many :notifications (creator role).
- has_many :viewable_notifications, through: :courses (student role).
3. Course Model:
- Associations:
- has_many :notifications.
- has_many :students, through: :enrollments.
Endpoints for Notification Management
Endpoints for Creating, Reading, Updating, and Deleting (CRUD) Notifications
1. POST /notifications - Create a new notification. The create method in the NotificationController implementation aligns with this endpoint, allowing authorized users to create notifications for specific courses.
2. GET /notifications - Get a list of notifications (with optional filters, such as is_active or is_unread). The index method aligns with this endpoint, allowing filtered retrieval of notifications.
3. GET /notifications/:id - View details of a specific notification. The show method aligns with this endpoint, retrieving specific notification details.
4. PUT /notifications/:id - Update a specific notification. The update method allows modification of existing notifications, respecting role-based access.
5. DELETE /notifications/:id - Delete a specific notification. The destroy method aligns with this endpoint, enabling notification deletion by authorized users.
6. PATCH /notifications/:id/toggle - Toggle active state of a notification. The toggle_notification_visibility method handles this functionality, toggling the is_active flag.
Design Pattern
1. Strategy Pattern
- Use Case:
- Dynamically applying different filtering criteria for notifications (e.g., active notifications, unread notifications, notifications for a specific course).
- This modularizes the filtering logic, making it easier to extend or change in the future.
- Explanation:
- The Strategy Pattern separates filtering logic into individual strategy classes, making it easy to add new filters without modifying existing code.
- It also enhances the maintainability and testability of the system.
2. Decorator Pattern
- Use Case:
- Dynamically modifying the presentation of notifications based on user roles or other conditions.
- For example, students might see a simplified view of notifications, while instructors see a detailed view.
- Explanation:
- The Decorator Pattern adds additional functionality (like role-based formatting) without modifying the original Notification model.
- It keeps the model clean and focused on core functionality.
3. Singleton Pattern
- Use Case:
- Managing a global notification scheduler or manager, ensuring that only one instance exists.
- Explanation:
- The Singleton Pattern ensures that there is only one centralized manager for handling notification-related tasks, such as scheduling or batch operations.
4. Chain of Responsibility Pattern
- Use Case:
- Modularizing the validation logic for notifications (e.g., checking permissions, validating expiration dates, ensuring uniqueness).
- Explanation:
- The Chain of Responsibility Pattern allows you to break down validation or processing logic into discrete steps, improving code readability and maintainability.
Following are the some of the SOLID principles with how they will be applied to the back-end development:
- Single Responsibility : Controllers handle HTTP requests, models handle data, and service objects handle business logic (e.g., sending notifications).
- Open/Closed : Extend functionality (e.g., new notification types or filters) by adding new classes or methods without modifying existing code.
- Interface Segregation : Create small, role-specific interfaces (e.g., policies for TAs, students, instructors) and separate service objects for different types of notifications.
Testing
This section outlines the comprehensive testing approach for the Notifications feature to ensure it meets functional and non-functional requirements. The strategy includes detailed test cases for models, controllers, role-based access, and error handling, leveraging RSpec for testing and RSwag for API documentation.
1. Unit Testing
Goal: Ensure that the Notification model functions as expected by validating its attributes, associations, and methods.
Key Test Cases:
- Validations:
- Presence validation for subject, description, expiration_date, course_id, and user_id.
- Length constraints on subject and description.
- Format validation for expiration_date.
- Associations:
- belongs_to :user and belongs_to :course associations are correctly established.
- Notifications have appropriate relationships with Course and User models.
- Custom Methods and Scopes:
- active_notifications: Ensure only active notifications are returned.
- unread_notifications: Validate that unread notifications are fetched correctly.
- Edge Cases:
- Notifications with an expired expiration_date should not appear in the active notifications list.
- Handling invalid foreign keys for user_id and course_id.
require 'rails_helper' RSpec.describe Notification, type: :model do describe 'validations' do it { should validate_presence_of(:subject) } it { should validate_presence_of(:description) } it { should validate_presence_of(:expiration_date) } it { should validate_length_of(:subject).is_at_most(100) } end describe 'associations' do it { should belong_to(:user) } it { should belong_to(:course) } end describe 'scopes' do let!(:active_notification) { create(:notification, is_active: true) } let!(:expired_notification) { create(:notification, expiration_date: Date.yesterday) } it 'returns active notifications' do expect(Notification.active_notifications).to include(active_notification) expect(Notification.active_notifications).not_to include(expired_notification) end end end
2. Controller Testing
Goal: Test all CRUD operations for the Notifications controller to ensure endpoints function as expected.
Key Test Cases:
- Index Action:
- Ensure all notifications for a given user are returned.
- Verify that unauthorized users cannot access the endpoint.
- Test paginated responses if applicable.
- Show Action:
- Ensure notifications can be retrieved by id.
- Handle cases where id is invalid or unauthorized.
- Create Action:
- Validate successful creation of notifications with proper attributes.
- Handle validation failures and unauthorized access.
- Update Action:
- Test updates to notification attributes.
- Ensure unauthorized users cannot perform updates.
- Delete Action:
- Verify notifications are deleted successfully.
- Test behavior when id is invalid or unauthorized.
require 'rails_helper' RSpec.describe Api::V1::NotificationsController, type: :controller do let(:admin) { create(:user, role: 'Admin') } let(:student) { create(:user, role: 'Student') } let(:notification) { create(:notification) } describe 'GET #index' do before { sign_in admin } it 'returns notifications for the current user' do get :index expect(response).to have_http_status(:ok) expect(json_response).to include(notification) end it 'denies access to unauthorized users' do sign_out admin sign_in student get :index expect(response).to have_http_status(:forbidden) end end describe 'POST #create' do context 'with valid attributes' do it 'creates a new notification' do expect { post :create, params: { notification: attributes_for(:notification) } }.to change(Notification, :count).by(1) end end context 'with invalid attributes' do it 'returns validation errors' do post :create, params: { notification: { subject: } } expect(response).to have_http_status(:unprocessable_entity) end end end end
3. Integration Testing
Goal: Ensure seamless interaction between models, controllers, and role-based access.
Key Test Cases:
- Role-Based Access:
- Verify only authorized roles (e.g., Admin, Instructor) can manage notifications.
- Ensure students can only view notifications for their enrolled courses.
- Full CRUD Workflow:
- Test end-to-end creation, updating, and deletion of notifications.
- Error Scenarios:
- Handle cases where a notification is created for a non-existent course or user.
- Verify appropriate error messages for unauthorized actions.
RSpec.describe 'Notifications Integration', type: :request do let(:admin) { create(:user, role: 'Admin') } let(:student) { create(:user, role: 'Student') } let!(:notification) { create(:notification, user: admin) } it 'allows admins to create notifications' do sign_in admin post '/api/v1/notifications', params: { notification: attributes_for(:notification) } expect(response).to have_http_status(:created) end it 'prevents students from creating notifications' do sign_in student post '/api/v1/notifications', params: { notification: attributes_for(:notification) } expect(response).to have_http_status(:forbidden) end end
4. API Documentation and Testing with RSwag
Goal: Generate API documentation and test all endpoints.
RSpec.describe 'Notifications API', type: :request do path '/api/v1/notifications' do get 'Retrieve notifications' do tags 'Notifications' produces 'application/json' response '200', 'success' do schema type: :array, items: { type: :object, properties: { id: { type: :integer }, subject: { type: :string } } } run_test! end end post 'Create a notification' do tags 'Notifications' consumes 'application/json' parameter name: :notification, in: :body, schema: { type: :object, properties: { subject: { type: :string }, description: { type: :string }, expiration_date: { type: :string, format: 'date' }, is_active: { type: :boolean } }, required: %w[subject description expiration_date] } response '201', 'created' do let(:notification) { { subject: 'Test', description: 'Test notification', expiration_date: '2024-12-31', is_active: true } } run_test! end end end end
Team
Mentor Jay Patel : jhpatel9@ncsu.edu
- Vaibhavi Shetty: vshetty2@ncsu.edu
- Soubarnica Suresh ssomang@ncsu.edu
- Aditi Killedar akilled@ncsu.edu
Relevant Links
- Github Repository: reimplementation back-end