OMG 200+ Downloads on!?

I published my first Ruby gem today, not just on GitHub but also on (so you can install it via $ gem install and run it just like any other gem, which feels pretty neat!).

It's a simple command line tool which uses Nokogiri to scrape the venerable and allows the user to query some detailed information about the ongoing sales (the most painful exclusion being 'time remaining', since Nokogiri can't see DOM elements inserted via Javascript… (;^;)).

I learned a couple things over the course of the project:

Ideal Object Relationships Aren't Always Obvious

For the most part, I was able to bring my idea to reality with very little friction; if you know HTML and CSS, learning to use Nokogiri is a breeze!

On top of that, it didn't take me long to settle on what classes my gem would be organized into:

  • I would need Bundle and Product classes
    • a Bundle should know about its Products
    • a Product should know about its Bundle
  • I would need a Scraper class to instantiate Bundles and Products
  • I would need a CLI to display formatted information queried from the Bundle and Product classes based on user input

This wasn't quite right, though. The hierarchy of a "Humble Bundle" looks like this:

    Bundle:             B
                |       |       |
  Donation      |       |       |
     Tiers:     T       T       T
               ---     ---     ---
              | | |   | | |   | | |
  Products:   P P P   P P P   P P P

Bundles are first organized into donation tiers, and the products fall under those ("Pay $7 or more to unlock such-and-such games!").

On my initial run, because they consisted of so little information (compared to bundles and products), I dismissed tiers as not deserving of their own Tier class, and represented them instead as strings:

  #=> ['$1+', '$8+', '$12+']

  #=> '$8+'

My other justification was that I wanted to be able to call Bundle#products to return an array of a bundle's products, and I felt that a layer of Tier objects would somehow get in the way (e.g. necessitating chained method calls like Bundle#tiers[2]#products).

I didn't want to store Product references in two different locations, either (e.g. within Tiers and within a separate flat array in @products). What if both needed to be modified later, or what if only one was modified by accident and they no longer matched?!

Just a healthy dose of (regrettably shallow) forethought, as I would realize later. ;)

Storing the tiers as strings made certain interactions awkward. For example, in order to print the formatted contents of a bundle to the terminal…


I needed to use a convoluted back-and-forth like this to get the message across:

bundle.tiers.each do |tier|

  puts tier

  bundle.products.each do |product|

    if product.tier == tier



So in order to print a single tier and its contents, the CLI needed to iterate over the Bundle's entire list of Products to find the right ones to print. Why should I need to ask both a Bundle and a Product to confirm whether a product falls under a tier?

Aside from that, relying on the comparison of strings could prove flimsy in the face of reckless programming. It's not uncommon for different bundles to have identical price tiers (almost every Humble Bundle starts off with "$1+" at the bottom tier, for example). If a tier from one bundle were being compared to the products list of another bundle, the CLI might very well find matches and print out the wrong information in the correct format! If instead, the tiers were all instances of their own class, it wouldn't matter whether they had the same description stored in a variable somewhere; object-to-object comparisons would be more reliable.

Probably dramatic considerations for a little text-parsing experiment (I already had the whole thing finished and working, after all), but the principle of it left a sour taste in my mouth.

In light of all this, and in the interest of not being a total slob, I added the Tier class, and remodeled the object relationship to look more like this:

  #=> [#<Tier:X>,#<Tier:Y>,#<Tier:Z>]

  #=> #<Bundle:Foo>
  #=> [#<Product:A>,#<Product:B>,#<Product:C>]

  #=> #<Tier:Z>
  #=> #<Bundle:Foo>

As just one of many examples, I was then able to consolidate the earlier printing nightmare…

bundle.tiers.each do |tier|

  puts tier

  bundle.products.each do |product|

    if product.tier == tier



…into something much more readable (no generous whitespace required!).

bundle.tiers.each do |tier|
  puts tier.description
  tier.products.each{|p| display_product(p)}

And as for my fear of not having easy access to a Bundle's Products, it's not as if Law prohibits writing a custom reader method:

class RumbleBundle::Bundle
  attr_accessor :tiers, # etc.

  def products
    self.tiers.collect{|t| t.products}.flatten


Crisis averted.

Convention Isn't Always Obvious

Since beginning to learn Ruby, I've heard - time and time again - that the Ruby convention is to place your executable file in the /bin directory.

Following the above, however (though my project boilerplate was generated with Bundler), I kept running into this error when trying to build and install my gem locally:

rake aborted!
WARNING:  See for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    ["exe/rumble_bundle"] are not files

I couldn't understand why it would be looking for an /exe directory. In the .gemspec file, I found spec.bindir pointed to "exe". Instead of googling to learn why, I thought "LOL no, the bin directory is /bin! Duh!" and changed it. And everything worked!

Later, in a moment of greater clarity, I googled "bundler exe" and found this post on the official Bundler blog. Turns out there are a few good reasons for adopting the 'new' folder structure (that is, adding a separate /exe directory beside /bin to contain executables intended for the final build).

Having switched my look-in directory to /bin, I was lucky I had also explicitly set my spec.executables property to 'rumble_bundle'; out of the box, Bundler's .gemspec is configured to make executables out of any file in the specified spec.bindir, which would have also included my console and setup files. Probably not good form.

You learn a new convention every day, it seems! And speaking of learning new things…

My Gem Was Not Downloaded by Over 200 People

After refreshing the page to see if RubyGems had accepted my day-one patch, I noticed my gem had gotten something like 70 downloads in under an hour or two. It would seem that RubyGems' robotic audience has grown substantially since 2012, when one could only expect roughly 30 downloads per version bump.

Perhaps appropriately, my scraper gem has gotten more attention from other scrapers than from real people.

Located in Dallas, TX and looking for full-time employment as a web developer.