Skip to Content
Course content

250: Building a Simple Event Ticketing System

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 Event class called refund_ticket(ticket_id). This method should find the ticket with the matching UUID, remove it from the @tickets array, 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 Ticket class called valid?. This method should return true only if the ticket is still present in its associated event's @tickets list. This prevents "ghost tickets" (tickets that were refunded but are still being presented at the door) from being used.

Test your code by:

  1. Creating an event with a capacity of 2.
  2. Issuing 2 tickets.
  3. Attempting to issue a 3rd ticket (should fail).
  4. Refunding one of the first two tickets using its ID.
  5. Issuing a new ticket (should now succeed).
  6. Checking valid? on the refunded ticket (should be false).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.