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
250: Building a Simple Event Ticketing System
I've seen this a dozen times in junior PRs: when asked to build a ticketing system, the first instinct is usually to treat the "available tickets" as a simple integer on the Event object. It feels intuitive. You have 100 seats, someone buys one, you decrement the number to 99. Easy, right?
Thinking the Event is Just a Counter
Here is why that approach falls apart the moment the requirements get real. Imagine your code looks something like this:
class Event
attr_accessor :tickets_available
def initialize(capacity)
@tickets_available = capacity
end
def sell_ticket
if @tickets_available > 0
@tickets_available -= 1
return true
end
false
end
end
This works for a "Hello World" version of a store, but it's a data integrity nightmare. What happens when a user wants a refund? You just increment the counter back up. But wait—how do you know which ticket was returned? What if the user tries to refund the same ticket twice? Since you aren't tracking individual tickets, you have no way to validate the transaction. You're tracking the quantity of things, but you aren't tracking the things themselves.
Deriving Availability from Ticket Objects
In a real system, the "truth" shouldn't be a number you manually increment or decrement. The truth should be the collection of issued tickets. If you have an event with a capacity of 100 and there are 40 Ticket objects associated with it in your database (or array), you have 60 tickets left. Period.
Let's rebuild this. We need an Event to define the limit, and a Ticket to represent the actual ownership. I like to keep the logic for issuing tickets inside the Event class to ensure the capacity is never exceeded—this is a basic example of encapsulation.
class Event
attr_reader :name, :capacity, :tickets
def initialize(name, capacity)
@name = name
@capacity = capacity
@tickets = [] # This is our source of truth
end
def available_tickets
@capacity - @tickets.length
end
def issue_ticket(customer_name)
if available_tickets > 0
ticket = Ticket.new(self, customer_name)
@tickets << ticket
puts "Ticket issued to #{customer_name}!"
ticket
else
puts "Sorry, #{@name} is sold out!"
nil
end
end
end
class Ticket
attr_reader :event, :customer_name, :id
def initialize(event, customer_name)
@event = event
@customer_name = customer_name
@id = SecureRandom.uuid # I always use UUIDs for tickets to prevent guessing
end
end
Now, look at how much more powerful this is. If we want to handle a refund, we don't just add 1 to a counter; we find the specific Ticket object and remove it from the @tickets array. The available_tickets method automatically reflects this change because it's calculating the value on the fly based on the actual objects present.
One small detail: I used SecureRandom.uuid. You'll need to require 'securerandom' at the top of your file for that. In the professional world, never use simple sequential IDs (1, 2, 3...) for things like tickets or order numbers. It makes it trivial for malicious users to scrape your data or guess other people's ticket IDs.
By shifting your mindset from "counting numbers" to "managing collections of objects," you've moved from writing a script to designing a system.
📋 Practical Task
Implementing Ticket Refund and Validation Logic
You have the basic system, but it's missing the ability to handle returns and verify authenticity. Your task is to extend the current implementation to make it production-ready.
Modify the Event and Ticket classes to implement the following:
- The Refund Method: Add a method to the
Eventclass calledrefund_ticket(ticket_id). This method should find the ticket with the matching UUID, remove it from the@ticketsarray, and return a success message. If the ID doesn't exist, it should return an error message. - The Validation Method: Add a method to the
Ticketclass calledvalid?. This method should returntrueonly if the ticket is still present in its associated event's@ticketslist. This prevents "ghost tickets" (tickets that were refunded but are still being presented at the door) from being used.
Test your code by:
- Creating an event with a capacity of 2.
- Issuing 2 tickets.
- Attempting to issue a 3rd ticket (should fail).
- Refunding one of the first two tickets using its ID.
- Issuing a new ticket (should now succeed).
- Checking
valid?on the refunded ticket (should befalse).
There are no comments for now.