Technical implementation specification for the Homeschool Master Rails API backend.
Set up a working Rails API with PostgreSQL, UUID primary keys, CORS, linting, and testing infrastructure. At the end of this phase, the server starts and responds to a health check.
| Gem | Purpose | Why This Phase | Why This Gem |
|---|---|---|---|
| rails | Web framework | Foundation of the entire API. | Only choice for a Rails API. |
| pg | PostgreSQL adapter | Need to connect to PostgreSQL database. | Only maintained PostgreSQL adapter for Rails. |
| puma | Web server | Need to serve HTTP requests. | Rails default, handles concurrent requests well. Falcon is faster but less battle-tested. |
| rack-cors | CORS middleware | Mobile app runs on different origin than API—browsers block cross-origin requests without CORS headers. | Standard choice, simple configuration. |
| dotenv-rails | Environment variable loading | Need to keep secrets out of code, load from .env files in development. | Simple and widely used. Alternatives like figaro work but dotenv is more common. |
| rspec-rails | Testing framework | Need to write tests from day one. | More expressive than Minitest, better community resources. Minitest is lighter but less flexible. |
| factory_bot_rails | Test data factories | Need to create test records without fixtures. | Industry standard for Rails. Fabrication is similar but less popular. |
| faker | Fake data generation | Need realistic test data (names, emails, etc.). | Most comprehensive fake data library. |
| rubocop-rails | Code linting | Enforce consistent code style from the start. | Standard Ruby linter with Rails-specific rules. |
| shoulda-matchers | Model test helpers | Simplifies testing validations and associations. | Makes model specs much cleaner. No real alternative. |
| database_cleaner-active_record | Test database cleanup | Need clean database state between tests. | Standard choice. Rails transactional tests work but can have edge cases. |
| Variable | Description |
|---|---|
| DATABASE_URL | PostgreSQL connection string |
| APP_HOST | API host URL (http://localhost:3000) |
| FRONTEND_URL | Mobile app URL (http://localhost:8081) |
app/
controllers/
api/
v1/
base_controller.rb
models/
services/
Location: app/controllers/api/v1/base_controller.rb
Phase 0 version has response helpers only. No authentication (added in Phase 1).
Required Capabilities:
| Response Type | Status | Structure |
|---|---|---|
| Success | 200 | { success: true, data: ..., meta?: ... } |
| Created | 201 | { success: true, data: ... } |
| No Content | 204 | Empty |
| Error | varies | { success: false, error: { code: ..., message: ..., details?: ... } } |
| Unauthorized | 401 | Error with code ‘UNAUTHORIZED’ |
| Forbidden | 403 | Error with code ‘FORBIDDEN’ |
| Not Found | 404 | Error with code ‘NOT_FOUND’ |
| Validation Error | 422 | Error with code ‘VALIDATION_ERROR’, details from model errors |
Phase 0 only defines:
Project Setup:
CORS:
Health Endpoint:
Base Controller Response Helpers:
Implement complete authentication with JWT tokens, teacher registration, login, logout, password reset, and email verification. At the end of this phase, users can register, log in, and access protected endpoints.
| Gem | Purpose | Why This Phase | Why This Gem |
|---|---|---|---|
| bcrypt | Password hashing for has_secure_password | Teacher model needs secure password storage for registration and login. | Rails’ built-in has_secure_password expects bcrypt. It’s the standard—no real alternatives here. |
| jwt | Encode/decode JSON Web Tokens | Authentication requires issuing access tokens and refresh tokens to clients. | The most popular Ruby JWT library. jose is an alternative but more complex and overkill for simple token auth. |
| Variable | Description | Default |
|---|---|---|
| JWT_SECRET_KEY | Secret for signing tokens | (required) |
| JWT_ACCESS_TOKEN_EXPIRY | Access token lifetime in seconds | 3600 |
| JWT_REFRESH_TOKEN_EXPIRY | Refresh token lifetime in seconds | 2592000 |
Teachers Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| first_name | string | Not null |
| last_name | string | Not null |
| nickname | string | Nullable |
| string | Not null, unique | |
| phone | string | Nullable |
| newsletter_subscribed | boolean | Default: false |
| password_digest | string | Not null |
| profile_image_url | string | Nullable |
| email_verified_at | datetime | Nullable |
| email_verification_token | string | Unique, nullable |
| password_reset_token | string | Unique, nullable |
| password_reset_sent_at | datetime | Nullable |
| is_active | boolean | Default: true |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Refresh Tokens Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| token | string | Not null, unique |
| jti | string | Not null, unique |
| expires_at | datetime | Not null |
| revoked_at | datetime | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Location: app/services/jwt_service.rb
Required Capabilities:
Configuration:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| first_name | presence, max length 100 |
| last_name | presence, max length 100 |
| nickname | max length 100 (optional) |
| presence, uniqueness (case insensitive), valid format | |
| password | minimum 8 characters (on create or when present) |
| phone | max length 20 (optional) |
Required Capabilities:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| token | presence, uniqueness |
| jti | presence, uniqueness |
| expires_at | presence |
Required Capabilities:
Add authentication to the base controller.
New Behaviors:
Authentication Logic:
Add to api/v1 namespace under auth namespace:
| Method | Path | Controller#Action |
|---|---|---|
| POST | /auth/register | authentication#register |
| POST | /auth/login | authentication#login |
| POST | /auth/refresh | authentication#refresh |
| POST | /auth/logout | authentication#logout |
| POST | /auth/password/reset-request | passwords#reset_request |
| POST | /auth/password/reset | passwords#reset |
| POST | /auth/password/change | passwords#change |
| POST | /auth/email/verify | email_verification#verify |
| POST | /auth/email/resend-verification | email_verification#resend |
Location: app/controllers/api/v1/auth/authentication_controller.rb
Skip authentication for: register, login, refresh, logout
Endpoints:
| Endpoint | Request | Success | Errors |
|---|---|---|---|
| register | first_name, last_name, email, password | 201, teacher data (no password_digest) | 422 validation errors |
| login | email, password | 200, access_token + refresh_token | 401 invalid credentials, 401 inactive |
| refresh | refresh_token | 200, new access_token | 401 invalid/expired/revoked |
| logout | refresh_token | 204 | 401 invalid token |
Login Flow:
Refresh Flow:
Location: app/controllers/api/v1/auth/passwords_controller.rb
Skip authentication for: reset_request, reset
Endpoints:
| Endpoint | Request | Success | Errors |
|---|---|---|---|
| reset_request | 200 (always, for security) | - | |
| reset | token, password | 200 | 400 invalid/expired token |
| change | current_password, new_password | 200 | 401 wrong current password |
Note: change requires authentication
Location: app/controllers/api/v1/auth/email_verification_controller.rb
Skip authentication for: verify
Endpoints:
| Endpoint | Request | Success | Errors |
|---|---|---|---|
| verify | token | 200 | 400 invalid token |
| resend | - | 200 | - |
Note: resend requires authentication
JWT Service:
Teacher Model:
Refresh Token Model:
Base Controller Authentication:
Authentication Controller:
Passwords Controller:
Email Verification Controller:
Add student profiles linked to teachers. Teachers can create, view, update, and delete their students.
| Gem | Purpose | Why This Phase | Why This Gem |
|---|---|---|---|
| kaminari | Pagination for ActiveRecord collections | Students index is the first list endpoint—can’t return unbounded results to mobile clients. | Clean API, well-maintained, plays nice with Rails. Pagy is faster but more manual setup. Will-paginate is older and less actively maintained. |
Students Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| first_name | string | Not null |
| last_name | string | Not null |
| nickname | string | Nullable |
| date_of_birth | date | Nullable |
| grade_level | string | Nullable |
| profile_image_url | string | Nullable |
| notes | text | Nullable |
| is_active | boolean | Default: true |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Teacher: Add association to students (destroyed when teacher deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| first_name | presence, max length 100 |
| last_name | presence, max length 100 |
| nickname | max length 100 (optional) |
| grade_level | max length 50 (optional) |
Required Capabilities:
Add pagination helper.
Pagination Behavior:
Add to api/v1 namespace:
Location: app/controllers/api/v1/students_controller.rb
Endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /students | List current teacher’s students (paginated) |
| GET | /students/:id | Show student details |
| POST | /students | Create student for current teacher |
| PATCH | /students/:id | Update student |
| DELETE | /students/:id | Delete student |
Index Filters:
Authorization: All actions scoped to current teacher’s students only
Student Model:
Students Controller:
Pagination Helper:
Add subjects and calendar events. Teachers can create subjects (like “Math” or “Science”) and schedule events on the calendar.
None—all required gems already installed.
Subjects Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| name | string | Not null |
| color | string | Nullable |
| description | text | Nullable |
| is_active | boolean | Default: true |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Calendar Events Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| student_id | uuid | Foreign key, nullable |
| subject_id | uuid | Foreign key, nullable |
| title | string | Not null |
| description | text | Nullable |
| start_time | datetime | Not null |
| end_time | datetime | Not null |
| all_day | boolean | Default: false |
| recurrence_rule | string | Nullable |
| location | string | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Teacher: Add associations to subjects and calendar events (destroyed when teacher deleted)
Student: Add association to calendar events (set null when student deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| name | presence, max length 100, unique per teacher |
| color | valid hex format (optional) |
Required Capabilities:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| title | presence, max length 255 |
| start_time | presence |
| end_time | presence, must be after start_time |
Required Capabilities:
Add to api/v1 namespace:
Location: app/controllers/api/v1/subjects_controller.rb
Standard CRUD scoped to current teacher’s subjects.
Location: app/controllers/api/v1/calendar_events_controller.rb
Endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /calendar_events | List events (requires start_date, end_date params) |
| GET | /calendar_events/:id | Show event details |
| POST | /calendar_events | Create event |
| PATCH | /calendar_events/:id | Update event |
| DELETE | /calendar_events/:id | Delete event |
Index Filters:
Create/Update Validation:
Subject Model:
Calendar Event Model:
Subjects Controller:
Calendar Events Controller:
Add assignments (homework, projects) and tasks (to-do items). Assignments are tied to students and optionally subjects. Tasks are standalone to-do items for the teacher.
None—all required gems already installed.
Assignments Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| student_id | uuid | Foreign key, not null |
| subject_id | uuid | Foreign key, nullable |
| title | string | Not null |
| description | text | Nullable |
| due_date | date | Nullable |
| status | string | Default: ‘pending’ |
| grade | string | Nullable |
| max_grade | string | Nullable |
| graded_at | datetime | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Status values: pending, in_progress, completed, graded
Foreign Keys:
Tasks Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| title | string | Not null |
| description | text | Nullable |
| due_date | date | Nullable |
| priority | string | Default: ‘medium’ |
| status | string | Default: ‘pending’ |
| completed_at | datetime | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Priority values: low, medium, high Status values: pending, completed
Teacher: Add associations to assignments and tasks (destroyed when teacher deleted)
Student: Add association to assignments (destroyed when student deleted)
Subject: Add association to assignments (set null when subject deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| title | presence, max length 255 |
| status | inclusion in valid values |
Required Capabilities:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| title | presence, max length 255 |
| priority | inclusion in valid values |
| status | inclusion in valid values |
Required Capabilities:
Add to api/v1 namespace:
Assignments:
Tasks:
Assignments Controller: app/controllers/api/v1/assignments_controller.rb
Tasks Controller: app/controllers/api/v1/tasks_controller.rb
Assignment Model:
Task Model:
Controllers:
Add report cards with grade entries per subject for each student.
None—all required gems already installed.
Report Cards Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| student_id | uuid | Foreign key, not null |
| title | string | Not null |
| period_start | date | Not null |
| period_end | date | Not null |
| notes | text | Nullable |
| issued_at | datetime | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Grade Entries Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| report_card_id | uuid | Foreign key, not null |
| subject_id | uuid | Foreign key, not null |
| grade | string | Not null |
| notes | text | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Teacher: Add association to report cards (destroyed when teacher deleted)
Student: Add association to report cards (destroyed when student deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| title | presence |
| period_start | presence |
| period_end | presence, must be after period_start |
Required Capabilities:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| grade | presence |
| subject_id | unique per report card |
Add to api/v1 namespace:
Report Cards:
Report Card Model:
Grade Entry Model:
Controllers:
Add expense categories and expenses for tracking homeschool costs.
None—all required gems already installed.
Expense Categories Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| name | string | Not null |
| color | string | Nullable |
| budget | decimal(10,2) | Nullable |
| is_active | boolean | Default: true |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Expenses Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| expense_category_id | uuid | Foreign key, nullable |
| student_id | uuid | Foreign key, nullable |
| description | string | Not null |
| amount | decimal(10,2) | Not null |
| date | date | Not null |
| vendor | string | Nullable |
| receipt_url | string | Nullable |
| notes | text | Nullable |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Teacher: Add associations to expense categories and expenses (destroyed when teacher deleted)
Student: Add association to expenses (set null when student deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| name | presence, unique per teacher |
| budget | numericality, greater than 0 (if present) |
Required Capabilities:
Behaviors:
Validations:
| Field | Rules |
|---|---|
| description | presence |
| amount | presence, numericality greater than 0 |
| date | presence |
Required Capabilities:
Add to api/v1 namespace:
Expense Category Model:
Expense Model:
Controllers:
Add lesson plans that tie together subjects, students, and calendar events.
None—all required gems already installed.
Lesson Plans Table:
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| teacher_id | uuid | Foreign key, not null |
| subject_id | uuid | Foreign key, nullable |
| title | string | Not null |
| description | text | Nullable |
| objectives | text | Nullable |
| materials | text | Nullable |
| procedure | text | Nullable |
| assessment | text | Nullable |
| duration_minutes | integer | Nullable |
| is_template | boolean | Default: false |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Lesson Plan Students Table (join):
| Column | Type | Constraints |
|---|---|---|
| id | uuid | Primary key |
| lesson_plan_id | uuid | Foreign key, not null |
| student_id | uuid | Foreign key, not null |
| created_at | datetime | Not null |
| updated_at | datetime | Not null |
Indexes:
Foreign Keys:
Teacher: Add association to lesson plans (destroyed when teacher deleted)
Subject: Add association to lesson plans (set null when subject deleted)
Behaviors:
Validations:
| Field | Rules |
|---|---|
| title | presence, max length 255 |
| duration_minutes | numericality, greater than 0 (if present) |
Required Capabilities:
Behaviors:
Validations:
Add to api/v1 namespace:
Lesson Plan Model:
Controllers:
Add production dependencies, file uploads, background jobs, and deployment configuration.
| Gem | Purpose | Why This Phase | Why This Gem |
|---|---|---|---|
| sidekiq | Background job processing | Email delivery (verification, password reset) should be async in production—can’t block requests waiting for SMTP. | Fast, battle-tested, great dashboard. Good_job is a solid DB-based alternative if you want to avoid Redis. Resque is older and slower. |
| redis | In-memory data store for Sidekiq | Required by Sidekiq for job queue storage. | Required by Sidekiq. No alternative if using Sidekiq. |
| aws-sdk-s3 | S3 file uploads for Active Storage | Profile images and receipt uploads need cloud storage in production—local disk doesn’t scale and doesn’t persist on Heroku. | Official AWS SDK. Shrine is an alternative to Active Storage entirely, but Active Storage is built-in and simpler to start. |
| image_processing | Resize/transform uploaded images | Profile images need thumbnails—can’t serve full-size images to mobile clients. | Required by Active Storage for variants. Uses libvips (fast) or ImageMagick under the hood. |
| Variable | Description |
|---|---|
| REDIS_URL | Redis connection string |
| AWS_ACCESS_KEY_ID | AWS access key |
| AWS_SECRET_ACCESS_KEY | AWS secret key |
| AWS_REGION | AWS region (e.g., us-east-1) |
| AWS_BUCKET | S3 bucket name |
Add attachments to models:
| Model | Attachment |
|---|---|
| Teacher | profile_image |
| Student | profile_image |
| Expense | receipt |
Create mailers for:
Use async delivery via background jobs.
File Uploads:
Background Jobs:
CORS:
After all phases, here are the complete associations:
Teacher:
Student:
Subject:
| Phase | Endpoints |
|---|---|
| 0 | GET /health |
| 1 | POST /api/v1/auth/* (register, login, refresh, logout, password/, email/) |
| 2 | GET/POST/PATCH/DELETE /api/v1/students |
| 3 | CRUD /api/v1/subjects, CRUD /api/v1/calendar_events |
| 4 | CRUD /api/v1/assignments (+ grade), CRUD /api/v1/tasks (+ complete) |
| 5 | CRUD /api/v1/report_cards (+ issue), nested grade_entries |
| 6 | CRUD /api/v1/expense_categories, CRUD /api/v1/expenses |
| 7 | CRUD /api/v1/lesson_plans (+ duplicate) |