Skip to content

sergio-fernandez-b/race-api-assignment

Repository files navigation

Race API Assignment

A Rails API application for managing racing data including races, teams, drivers, cars, and lap times.

Table of Contents

Database Design

Entity Relationship Diagram

┌──────────┐         ┌──────────┐         ┌─────────┐
│  Driver  │         │   Team   │         │  Race   │
└────┬─────┘         └────┬─────┘         └────┬────┘
     │                    │                    │
     │          ┌─────────┘                    │
     └──────────|           ┌────────┬─────────┘
                │           │        │
                │           │        │
           ┌────▼────┐      │   ┌────▼────┐
           │   Car   │──────┘   │   Lap   │
           └────┬────┘          └────┬────┘
                │                    │
                └────────────────────┘

Models and Relationships

Teams

  • Fields: id, name (unique), created_at, updated_at
  • Relationships:
    • has_many :cars

Drivers

  • Fields: id, first_name, last_name, birth_date, created_at, updated_at
  • Relationships:
    • has_many :cars
  • Validations:
    • First name and last name combination must be unique
    • Birth date must be in the past

Races

  • Fields: id, name, track_name, start_time, end_time, start_latitude, start_longitude, finish_latitude, finish_longitude, created_at, updated_at
  • Relationships:
    • has_many :cars
    • has_many :laps
  • Validations:
    • Coordinates must be within valid ranges (-90 to 90 for latitude, -180 to 180 for longitude)
    • Start time must be before end time

Cars

  • Fields: id, car_number, race_id, driver_id, team_id, created_at, updated_at
  • Relationships:
    • belongs_to :race
    • belongs_to :driver
    • belongs_to :team
    • has_many :laps
  • Validations:
    • Car number must be unique within a race
    • Car number must be >= 1

Laps

  • Fields: id, lap_number, race_id, car_id, start_time, end_time, lap_time_ms, created_at, updated_at
  • Relationships:
    • belongs_to :race
    • belongs_to :car
  • Validations:
    • Lap number must be unique per car per race
    • Start time must be before end time
    • Lap time must be > 0

Database Constraints

  • Unique Constraints:

    • Team names must be unique
    • Driver first_name + last_name combination must be unique
    • Car number must be unique within a race
    • Lap number must be unique per car per race
    • Race name + track_name + start_time combination must be unique
  • Foreign Keys:

    • Cars reference: races, drivers, teams
    • Laps reference: races, cars

API Routes

All API endpoints are under the /api/v1 namespace and return JSON responses in JSON:API format.

Base URL

http://localhost:3000/api/v1

Races

Method Endpoint Description
GET /races List all races (paginated)
GET /races/:id Get a specific race
POST /races Create a new race
PATCH /races/:id Update a race
DELETE /races/:id Delete a race
GET /races/:id/leaderboard Get race leaderboard

Teams

Method Endpoint Description
GET /teams List all teams (paginated)
GET /teams/:id Get a specific team
POST /teams Create a new team
PATCH /teams/:id Update a team
DELETE /teams/:id Delete a team
GET /stats/teams/:id Get team statistics

Drivers

Method Endpoint Description
GET /drivers List all drivers (paginated)
GET /drivers/:id Get a specific driver
POST /drivers Create a new driver
PATCH /drivers/:id Update a driver
DELETE /drivers/:id Delete a driver
GET /stats/drivers/:id Get driver statistics

Cars (Nested under Races)

Method Endpoint Description
GET /races/:race_id/cars List all cars in a race (paginated)
GET /races/:race_id/cars/:car_number Get a specific car by car number
POST /races/:race_id/cars Create a new car in a race
PATCH /races/:race_id/cars/:car_number Update a car
DELETE /races/:race_id/cars/:car_number Delete a car

Laps (Nested under Races)

Method Endpoint Description
GET /races/:race_id/laps List all laps in a race (paginated)
GET /races/:race_id/laps/:lap_number Get a specific lap by lap number
POST /races/:race_id/cars/:car_number/laps Create a new lap for a car
PATCH /races/:race_id/cars/:car_number/laps/:lap_number Update a lap
DELETE /races/:race_id/cars/:car_number/laps/:lap_number Delete a lap
GET /races/:race_id/cars/:car_number/laps Get all laps for a specific car

Query Parameters

  • Pagination:
    • page - Page number (default: 1)
    • per_page - Items per page (default: 25, max: 100)

API Usage Examples

Creating a Race

curl -X POST http://localhost:3000/api/v1/races \
  -H "Content-Type: application/json" \
  -d '{
    "race": {
      "name": "F1 Grand Prix 2026",
      "track_name": "Monaco",
      "start_time": "2026-05-29 14:00:00",
      "end_time": "2026-05-29 16:00:00",
      "start_latitude": 43.7347,
      "start_longitude": 7.4206,
      "finish_latitude": 43.7347,
      "finish_longitude": 7.4206
    }
  }'

Response:

{
  "data": {
    "id": "1",
    "type": "races",
    "attributes": {
      "name": "F1 Grand Prix 2026",
      "track-name": "Monaco",
      "start-time": "2026-05-29 14:00:00",
      "end-time": "2026-05-29 16:00:00",
      "start-latitude": 43.7347,
      "start-longitude": 7.4206,
      "finish-latitude": 43.7347,
      "finish-longitude": 7.4206
    },
    "links": {
      "self": "/api/v1/races/1"
    }
  }
}

Creating a Team

curl -X POST http://localhost:3000/api/v1/teams \
  -H "Content-Type: application/json" \
  -d '{
    "team": {
      "name": "Mercedes"
    }
  }'

Creating a Driver

curl -X POST http://localhost:3000/api/v1/drivers \
  -H "Content-Type: application/json" \
  -d '{
    "driver": {
      "first_name": "Lewis",
      "last_name": "Hamilton",
      "birth_date": "1985-01-07"
    }
  }'

Creating a Car

curl -X POST http://localhost:3000/api/v1/races/1/cars \
  -H "Content-Type: application/json" \
  -d '{
    "car": {
      "car_number": 44,
      "driver_id": 1,
      "team_id": 1
    }
  }'

Creating a Lap

curl -X POST http://localhost:3000/api/v1/races/1/cars/44/laps \
  -H "Content-Type: application/json" \
  -d '{
    "lap": {
      "lap_number": 1,
      "start_time": "2026-05-29 14:05:00",
      "end_time": "2026-05-29 14:06:15",
      "lap_time_ms": 75915
    }
  }'

Getting Paginated Results

# Get first page with 10 items per page
curl "http://localhost:3000/api/v1/races?page=1&per_page=10"

Response:

{
  "data": [
    {
      "id": "1",
      "type": "races",
      "attributes": { ... }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 10,
    "total_pages": 3,
    "total_count": 25
  }
}

Getting Race Leaderboard

curl http://localhost:3000/api/v1/races/1/leaderboard

Getting Team Statistics

curl http://localhost:3000/api/v1/stats/teams/1

Getting Driver Statistics

curl http://localhost:3000/api/v1/stats/drivers/1

Getting All Laps for a Car

curl http://localhost:3000/api/v1/races/1/cars/44/laps

Error Responses

All errors follow a consistent format:

{
  "error": "Validation failed",
  "details": [
    "Name can't be blank",
    "Start time must be before end time"
  ]
}

Common HTTP Status Codes:

  • 200 OK - Successful GET, PATCH
  • 201 Created - Successful POST
  • 204 No Content - Successful DELETE
  • 400 Bad Request - Invalid request parameters
  • 404 Not Found - Resource not found
  • 422 Unprocessable Entity - Validation errors
  • 500 Internal Server Error - Server error

Setup

Prerequisites

  • Ruby 2.7+ (or as specified in .ruby-version)
  • Bundler
  • SQLite3

Installation

  1. Copy the race_api_assingment folder in your computer

  2. Install dependencies:

bundle install
  1. Set up the database:
rails db:create
rails db:migrate
rails db:seed
  1. Start the server:
rails server

The API will be available at http://localhost:3000

Testing

Running Tests

# Run all tests
bundle exec rspec

# Run specific test files
bundle exec rspec spec/models
bundle exec rspec spec/requests

Test Structure

  • Model Specs (spec/models/): Test validations, associations, and model methods
  • Request Specs (spec/requests/): Test API endpoints and responses
  • Factories (spec/factories/): Test data factories using FactoryBot

Test Coverage

The test suite includes:

  • Model validations and associations
  • API endpoint functionality
  • Error handling
  • Pagination
  • Nested resource operations

Technology Stack (with Sergio's comments)

  • Framework: Ruby on Rails 8.1.2
  • Database: SQLite3
  • Serialization: ActiveModel::Serializers (JSON:API adapter)
    • Sergio's trade off:
      • Pros: Allows me to return JSON:Api responses pretty simple, and it's well known between devs, so it's easy to understand.
      • Cons: I know there are best options out there like Fast JSON API, but I didn't have the time to try it before this assignment.
  • Pagination: Kaminari
    • Sergio's trade off:
      • Pro: I like because is more Rails way, cleaner and okay for a smaller API.
      • Con: It's a bit slower and could use more memory than other gems.
  • Testing: RSpec, FactoryBot, Shoulda Matchers
    • Sergio's trade off:
      • Pro: It's an industry standard
      • Con: I prioritized feature completion over full test-suite optimization since I was running out of time, sorry!

Notes

  • All timestamps are in UTC
  • Lap times are stored in milliseconds (bigint)
  • Car numbers and lap numbers are used as route parameters instead of IDs for nested routes
  • The API uses JSON:API format, which converts snake_case attributes to kebab-case in responses
  • Relations (associations) are only included in single resource responses, not in collection responses

About

A basic Restful API for race cars (F1, Nascar)

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages