🌐 Live Demo: https://libraryhub-yangzhao-1b53d70f2081.herokuapp.com/
| Name | Password | Role | |
|---|---|---|---|
| Admin User | admin@libraryhub.com | password123 | admin |
| Alice Smith | alice@example.com | password123 | user |
| Bob Jones | bob@example.com | password123 | user |
SEP 759 Prototyping Web Applications — Final Project (Phase 1 & 2) McMaster University, Winter 2026
- Project Description
- Submission Checklist
- Technology Stack
- Project Structure
- Local Setup & Running the Application
- Test Accounts
- Application Features
- API Endpoints & Testing with Postman
- Heroku Deployment
- ER Diagram & Application Flow
LibraryHub is a Ruby on Rails web application that implements a full library management system. Users can browse books, borrow and return them, and leave reviews. Administrators manage the book catalog and monitor all borrowing activity.
The project is built in two phases:
- Phase 1 — Full MVC web application with custom authentication (has_secure_password + sessions), nested resources, all four model relationship types, DRY partials, and seed data.
- Phase 2 — Authentication migrated to Devise gem, plus a complete JWT-secured RESTful JSON API with 21 endpoints, tested via Postman.
| Requirement | Status | Details |
|---|---|---|
| At least 4 models | ✅ | User, Book, Category, Borrow, Review, BookCategory (6 models) |
| At least 1 nested resource | ✅ | books/:book_id/reviews and books/:book_id/borrows |
| one-to-one relationship | — | Not required (spec says "at least 3 of 4") |
| one-to-many relationship | ✅ | User → Borrows, User → Reviews, Book → Borrows, Book → Reviews |
| many-to-many relationship | ✅ | Books ↔ Categories via book_categories |
| rich one-to-many (has-through) | ✅ | User → Books through Borrows |
| User model with authentication | ✅ | has_secure_password (Phase 1), Devise (Phase 2) |
| Sessions for logged-in users | ✅ | session[:user_id] stores current user |
| DRY using partials | ✅ | _navbar, _flash, _book_card, _form (books & categories) |
| No Devise in Phase 1 | ✅ | Custom auth from scratch |
| Application is functional | ✅ | All CRUD operations working |
| Hosted on Heroku | ✅ | https://libraryhub-yangzhao-1b53d70f2081.herokuapp.com/ |
| CSS in stylesheet (no inline) | ✅ | All styles in application.css |
| Seed data | ✅ | rails db:seed — 3 users, 4 books, 4 categories, borrows, reviews |
| Test accounts | ✅ | See Section 6 |
| ER diagram | ✅ | See LibraryHub_Proposal.docx |
| Application flow document | ✅ | See LibraryHub_Proposal.docx |
| Requirement | Status | Details |
|---|---|---|
| At least 5 API endpoints | ✅ | 21 endpoints total |
| Devise gem for authentication | ✅ | Replaces custom auth |
| API user authentication (JWT) | ✅ | Bearer token via Authorization header |
| API testing — user signup/login | ✅ | POST /api/v1/auth/register and /login |
| API testing — GET collection | ✅ | GET /api/v1/books, /categories, /borrows |
| API testing — GET single item | ✅ | GET /api/v1/books/:id, /categories/:id |
| Bonus: image upload | — | Not implemented |
| Bonus: POST/PATCH/DELETE tested | ✅ | Borrow, return, delete review via API |
| Bonus: notification emails | — | Not implemented |
| Video demonstration | See submission | |
| Seed data on Heroku | See Section 9 | |
| Test accounts | ✅ | See Section 6 |
| ER diagram | ✅ | See LibraryHub_Proposal.docx |
| Component | Technology |
|---|---|
| Framework | Ruby on Rails 7.1.6 |
| Language | Ruby 3.1.4 |
| Database (local) | SQLite3 |
| Database (Heroku) | PostgreSQL |
| Authentication | Devise gem + custom sessions (Phase 1) |
| API Auth | JWT (JSON Web Tokens) |
| Frontend | ERB, Turbo Rails, Stimulus JS |
| Styling | Custom CSS (application.css) |
| API Testing | Postman |
| Deployment | Heroku |
library_hub/
├── app/
│ ├── controllers/
│ │ ├── api/
│ │ │ └── v1/
│ │ │ ├── base_controller.rb # JWT auth for all API routes
│ │ │ ├── auth_controller.rb # login / register / logout
│ │ │ ├── books_controller.rb # API books CRUD
│ │ │ ├── categories_controller.rb # API categories CRUD
│ │ │ ├── borrows_controller.rb # API borrow / return
│ │ │ ├── reviews_controller.rb # API reviews
│ │ │ └── users_controller.rb # API user profile
│ │ ├── users/
│ │ │ ├── sessions_controller.rb # Devise sessions
│ │ │ └── registrations_controller.rb # Devise registrations
│ │ ├── application_controller.rb
│ │ ├── books_controller.rb
│ │ ├── borrows_controller.rb
│ │ ├── categories_controller.rb
│ │ └── reviews_controller.rb
│ ├── models/
│ │ ├── user.rb
│ │ ├── book.rb
│ │ ├── category.rb
│ │ ├── borrow.rb
│ │ ├── review.rb
│ │ └── book_category.rb
│ ├── views/
│ │ ├── shared/
│ │ │ ├── _navbar.html.erb # partial: navigation bar
│ │ │ ├── _flash.html.erb # partial: flash messages
│ │ │ └── _book_card.html.erb # partial: reusable book card
│ │ ├── books/
│ │ │ ├── _form.html.erb # partial: shared book form
│ │ │ ├── index.html.erb
│ │ │ ├── show.html.erb
│ │ │ ├── new.html.erb
│ │ │ └── edit.html.erb
│ │ ├── categories/
│ │ │ ├── _form.html.erb # partial: shared category form
│ │ │ ├── index.html.erb
│ │ │ ├── show.html.erb
│ │ │ ├── new.html.erb
│ │ │ └── edit.html.erb
│ │ ├── borrows/
│ │ │ └── index.html.erb
│ │ └── users/
│ │ ├── sessions/new.html.erb
│ │ ├── registrations/new.html.erb
│ │ ├── show.html.erb
│ │ └── edit.html.erb
│ ├── assets/stylesheets/
│ │ └── application.css # all custom styles (no inline CSS)
│ └── lib/
│ └── json_web_token.rb # JWT encode/decode helper
├── config/
│ ├── routes.rb # all web + API routes
│ ├── initializers/
│ │ └── cors.rb # CORS config for API
│ └── database.yml
├── db/
│ ├── migrate/
│ └── seeds.rb # seed data for all environments
└── Gemfile
Make sure you have the following installed:
- Ruby 3.1.4 — rubyinstaller.org (Windows) or rbenv/rvm (Mac/Linux)
- Bundler — comes with Ruby, or run
gem install bundler - Git
git clone https://github.com/YangZ0225/library-hub.git
cd library-hubbundle installIf you are on Windows and encounter native extension errors, run:
ridk install
# Choose option 3 (MSYS2 and MINGW development toolchain)Then retry bundle install.
rails db:create
rails db:migrate
rails db:seedThis creates the SQLite database, runs all migrations, and loads seed data including 3 users, 4 books, 4 categories, sample borrows, and sample reviews.
rails serverThe application will be available at:
http://localhost:3000
Use one of the test accounts from Section 6 to log in. The admin account can add/edit/delete books and categories.
The following accounts are created automatically by rails db:seed:
| Name | Password | Role | |
|---|---|---|---|
| Admin User | admin@libraryhub.com | password123 | admin |
| Alice Smith | alice@example.com | password123 | user |
| Bob Jones | bob@example.com | password123 | user |
Admin capabilities:
- Add, edit, and delete books and categories
- View all users' borrow records
- Delete any review
- Full CRUD access via the API
User capabilities:
- Browse all books and categories
- Borrow available books (14-day loan period)
- Return borrowed books
- Write one review per book
- View and edit their own profile
- Browse all books with title, author, category tags, availability, and average rating
- Search by title or author; filter by category
- View full book details including all reviews
- Borrow available books with one click
- Return borrowed books from the book detail page or My Borrows page
- Browse all categories with book counts
- View all books belonging to a category
- Admin: create, edit, delete categories
- Users see their own active and past borrow history
- Admins see all borrow records across all users
- Status automatically tracks:
active,returned,overdue
- Logged-in users can submit one review per book (1–5 stars + text)
- Reviews display on the book detail page with star ratings and dates
- Users can delete their own reviews; admins can delete any review
- Registration, login, logout via Devise
- Password recovery via email (configured with default mailer settings)
- Rememberable session option on login form
The API base URL is:
http://localhost:3000/api/v1
All API responses are JSON. Protected endpoints require the following header:
Authorization: Bearer <your_jwt_token>
POST /api/v1/auth/register
Body (raw JSON):
{
"name": "Test User",
"email": "test@example.com",
"password": "password123",
"password_confirmation": "password123"
}Expected response 201 Created:
{
"message": "Registration successful",
"token": "eyJhbGciOiJIUzI1NiJ9...",
"user": { "id": 4, "name": "Test User", "email": "test@example.com", "role": "user" }
}POST /api/v1/auth/login
Body (raw JSON):
{
"email": "admin@libraryhub.com",
"password": "password123"
}Expected response 200 OK:
{
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiJ9...",
"user": { "id": 1, "name": "Admin User", "email": "admin@libraryhub.com", "role": "admin" }
}Copy the token value. In Postman, set it as an environment variable or paste it into the Authorization header as Bearer <token> for all subsequent requests.
GET /api/v1/books
Expected response 200 OK:
[
{
"id": 1,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"isbn": "978-0743273565",
"available_copies": 4,
"total_copies": 5,
"average_rating": 4.0,
"categories": [{ "id": 1, "name": "Fiction" }]
},
...
]GET /api/v1/books/1
Expected response 200 OK — includes full description and all reviews.
POST /api/v1/borrows
Header: Authorization: Bearer <token>
Body (raw JSON):
{
"book_id": 2
}Expected response 201 Created:
{
"id": 3,
"book": { "id": 2, "title": "Clean Code" },
"borrowed_at": "2026-03-29",
"due_date": "2026-04-12",
"status": "active"
}PATCH /api/v1/borrows/3
Header: Authorization: Bearer <token>
No body required.
Expected response 200 OK:
{
"message": "Book returned",
"status": "returned"
}POST /api/v1/reviews
Header: Authorization: Bearer <token>
Body (raw JSON):
{
"book_id": 3,
"rating": 5,
"content": "An absolutely fascinating read. Highly recommended."
}Expected response 201 Created.
DELETE /api/v1/reviews/1
Header: Authorization: Bearer <admin_token>
Expected response 200 OK:
{ "message": "Review deleted" }| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/auth/register |
No | Register new user |
| POST | /api/v1/auth/login |
No | Login, returns JWT |
| DELETE | /api/v1/auth/logout |
Yes | Logout |
| GET | /api/v1/books |
No | List all books |
| GET | /api/v1/books/:id |
No | Book detail + reviews |
| POST | /api/v1/books |
Admin | Create book |
| PATCH | /api/v1/books/:id |
Admin | Update book |
| DELETE | /api/v1/books/:id |
Admin | Delete book |
| GET | /api/v1/categories |
No | List categories |
| GET | /api/v1/categories/:id |
No | Category + books |
| POST | /api/v1/categories |
Admin | Create category |
| DELETE | /api/v1/categories/:id |
Admin | Delete category |
| GET | /api/v1/borrows |
Yes | My borrows (admin: all) |
| POST | /api/v1/borrows |
Yes | Borrow a book |
| PATCH | /api/v1/borrows/:id |
Yes | Return a book |
| DELETE | /api/v1/borrows/:id |
Admin | Delete borrow record |
| GET | /api/v1/reviews |
No | List reviews |
| POST | /api/v1/reviews |
Yes | Submit review |
| DELETE | /api/v1/reviews/:id |
Yes | Delete review |
| GET | /api/v1/users/:id |
Yes | User profile |
| PATCH | /api/v1/users/:id |
Yes | Update profile |
- Heroku CLI installed: devcenter.heroku.com/articles/heroku-cli
- Heroku account created
In Gemfile, add:
group :production do
gem "pg"
endAnd move sqlite3 to development/test only:
group :development, :test do
gem "sqlite3"
endRun bundle install.
In config/database.yml, the production section should use the DATABASE_URL environment variable (Heroku sets this automatically when you add the Postgres addon).
heroku login
heroku create library-hub-yournameheroku addons:create heroku-postgresql:miniheroku config:set RAILS_MASTER_KEY=$(cat config/master.key)
heroku config:set SECRET_KEY_BASE=$(rails secret)git add .
git commit -m "Deploy to Heroku"
git push heroku mainheroku run rails db:migrate
heroku run rails db:seedheroku openThe live URL will be: https://libraryhub-yangzhao-1b53d70f2081.herokuapp.com/
See the file LibraryHub_Proposal.docx included in the submission for:
- Full database Entity Relationship Diagram with all attributes and data types
- Complete application flow description
- Model relationship types (one-to-many, many-to-many, rich has-through)
- All API endpoints documented
users ──< borrows >── books
users ──< reviews >── books
books >──< categories (via book_categories)
| Relationship | Type |
|---|---|
| User → Borrows | one-to-many |
| User → Reviews | one-to-many |
| Book → Borrows | one-to-many |
| Book → Reviews | one-to-many |
| Books ↔ Categories | many-to-many (via book_categories) |
| User → Books via Borrows | rich one-to-many (has-through) |
- On Windows, if you encounter gem native extension errors on first setup, run
ridk installand choose option 3 before runningbundle install. - The
config/master.keyfile is not committed to version control. Keep this file safe — it is required to run the application. - To reset the database completely:
rails db:drop db:create db:migrate db:seed - The API uses JWT tokens that expire after 24 hours. If you receive a 401 Unauthorized response, log in again to get a fresh token.
LibraryHub — SEP 759 Final Project, McMaster University, Winter 2026