Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
162: Building a Simple Task Management API
How do I actually structure the routes so they feel like a real REST API?
When you're building an API, the goal is predictability. You don't want to invent names like /get_all_tasks or /update_task_now. Instead, we use the HTTP verbs to describe the action and the URL to describe the resource. For a task manager, your resource is /tasks.
I usually stick to this pattern: GET /tasks to list everything, POST /tasks to create one, and then use a dynamic ID (like /tasks/:id) for specific actions. Here is how that looks in a Sinatra setup:
require 'sinatra'
require 'json'
# In-memory store for now
tasks = {}
get '/tasks' do
content_type :json
tasks.values.to_json
end
post '/tasks' do
content_type :json
data = JSON.parse(request.body.read)
id = tasks.keys.max.to_i + 1
new_task = { id: id, title: data['title'], completed: false }
tasks[id] = new_task
status 201
new_task.to_json
end
Do I really need a full database for a simple prototype, or can I just use a hash?
Honestly? For a prototype or a coding challenge, a hash is plenty. It keeps the setup fast and avoids the headache of managing migrations and connection strings while you're still figuring out your API logic. I used a hash in the example above because it allows for O(1) lookup time when you're trying to find a task by its ID.
Just keep in mind that the moment you restart your server, your data vanishes. That's the trade-off. Once you're happy with the interface, swapping that hash for a database call using ActiveRecord or Sequel is usually a pretty straightforward refactor because the "shape" of the data stays the same.
How do I handle JSON requests and responses without it getting messy?
Ruby doesn't automatically turn a request body into a hash, which is a common point of confusion. You have to explicitly read the request body and parse it. I always recommend setting the content_type :json at the top of your routes so the client (like Postman or a frontend app) knows exactly what it's receiving.
If you find yourself repeating JSON.parse(request.body.read) in every single POST and PUT route, just move it into a helper method. It keeps your route handlers clean and focused on the business logic rather than the plumbing.
helpers do
def json_params
JSON.parse(request.body.read)
rescue
halt 400, { error: "Invalid JSON" }.to_json
end
end
put '/tasks/:id' do
content_type :json
id = params[:id].to_i
halt 404, { error: "Task not found" }.to_json unless tasks.key?(id)
tasks[id].merge!(json_params)
tasks[id].to_json
end
What's the best way to handle cases where a task doesn't exist?
Don't let your app crash with a 500 Internal Server Error just because a user typed the wrong ID. That's a bad experience. Instead, use the halt method in Sinatra to stop execution immediately and return a 404 Not Found.
I've seen a lot of beginners return a 200 OK with an error message in the body, but that's a "REST crime." If the resource isn't there, the HTTP status code should say so. It makes your API much easier to debug for whoever is consuming it.
delete '/tasks/:id' do
content_type :json
id = params[:id].to_i
if tasks.delete(id)
status 204 # No Content is the standard for successful deletes
else
halt 404, { error: "Can't delete what doesn't exist" }.to_json
end
end
📋 Practical Task
Implementing a Priority Filter for the Task List
Right now, the GET /tasks endpoint returns every single task in the system. In a real app, that would eventually slow everything down. Your goal is to modify the GET /tasks route to support an optional priority query parameter.
- Update the task creation logic (
POST /tasks) to allow an optionalpriorityfield (e.g., "high", "medium", "low"). - Modify the
GET /tasksroute so that if a user visits/tasks?priority=high, the API only returns tasks with that specific priority. - If no priority parameter is provided, the API should continue to return all tasks as it does now.
There are no comments for now.