Skip to Content
Course content

248: Building a Social Bookmarking Service

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

By now, you've got a handle on the core syntax and object-oriented patterns of Ruby. Today, we're going to put that into practice by building the engine for a social bookmarking service. We aren't worrying about the database or the web frontend right now—we're focusing on the domain logic: how we store, tag, and retrieve links efficiently.

Designing the Bookmark Object

I like to start by defining a simple data structure. A bookmark isn't just a URL; it needs a title and a set of tags. I'll use a Struct here because it's a clean way to create a class that primarily holds data without needing a bunch of boilerplate attr_accessor calls.

Bookmark = Struct.new(:id, :url, :title, :tags)

Now we need a manager class to handle the collection. I'll call it BookmarkService. This is where the "social" part begins—even though we're starting with a single-user perspective, we want the architecture to support scaling to many users later.

Managing the Collection

In my first pass, I'll create a class that holds an array of bookmarks. I want to be able to add a bookmark and then find all bookmarks associated with a specific tag. Here's how I'd initially sketch that out:

class BookmarkService
  def initialize
    @bookmarks = []
    @next_id = 1
  end

  def add_bookmark(url, title, tags = [])
    bookmark = Bookmark.new(@next_id, url, title, tags)
    @bookmarks < < bookmark
    @next_id += 1
    bookmark
  end

  def find_by_tag(tag)
    @bookmarks.select { |b| b.tags.include?(tag) }
  end
end

The "Wait, this is slow" Moment

I've made a classic mistake here. I'm using select to filter bookmarks by tag. For ten bookmarks, it's fine. For ten thousand? We're iterating through the entire list every single time someone wants to see their "Ruby" tags. That's an O(n) operation, and in a real-world service, that's a performance killer.

I need an index. Instead of just a list, I'll maintain a hash where the keys are tags and the values are arrays of bookmarks. I'll rewrite the add_bookmark method to update this index on the fly. It adds a bit of complexity to the write operation, but it makes the read operation nearly instantaneous.

class BookmarkService
  def initialize
    @bookmarks = []
    @tag_index = Hash.new { |hash, key| hash[key] = [] }
    @next_id = 1
  end

  def add_bookmark(url, title, tags = [])
    bookmark = Bookmark.new(@next_id, url, title, tags)
    @bookmarks < < bookmark
    
    # Update the index for each tag
    tags.each { |tag| @tag_index[tag] < < bookmark }
    
    @next_id += 1
    bookmark
  end

  def find_by_tag(tag)
    @tag_index[tag]
  end
end

Adding Search and Refinement

To make this actually useful, we need more than just tag lookups. I want to be able to search titles for keywords. I'll add a search method using a regular expression. I'm opting for a case-insensitive search because users rarely remember exactly how they capitalized a title six months ago.

class BookmarkService
  # ... previous methods ...

  def search(query)
    regex = Regexp.new(query, Regexp::IGNORECASE)
    @bookmarks.select { |b| b.title =~ regex }
  end
end

Now we have a functioning core. We can add links, categorize them via an index for speed, and search through them using regex. It's a lean implementation, but it follows the primary rule of software engineering: solve the immediate problem, but don't build a system that collapses the moment your data grows.




📋 Practical Task

Implementing a Duplicate URL Guard

Right now, the BookmarkService allows a user to add the same URL multiple times, which would clutter any real bookmarking app. Your task is to modify the BookmarkService class to prevent duplicate URLs.

  • Modify the add_bookmark method so that it checks if the URL already exists in the @bookmarks collection.
  • If the URL exists, the method should return nil (or raise a custom error) instead of adding the bookmark and incrementing the ID.
  • Ensure that the @tag_index is not updated if the bookmark is a duplicate.

Test your implementation by attempting to add the same URL twice and verifying that the second call does not increase the total count of bookmarks.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.