Polish Your Gems

As Ruby developers, we often forget how good we have it. We’ve got a awesome library distribution system in Rubygems, we use a powerful and flexible language that just begs to be used for DSLs and a culture of open development and community improvement. In this article, I’ll be talking about the ways you can add that bit of extra polish to your useful new gem. We’ll look at some common usage patterns and how they’re achieved in popular gems.

None of these tips provide any extra functionality for your gem – but they do provide a good user experience. If you have ambitions for your gem to become popular, UI matters and code is a programmers UI.

The Config Block

If you’ve been a rubyist for even a short while, you’ve seen this. The method I’m going to show use is useful for global set up of the gem. Let’s take a look:

So, there’s a few things to notice here. Firstly, I’ve chosen to have the Configuration object inherit from the fantastic hashie mash gem. This allows us to treat our Configuration object as a hash internally, but means we can expose nice dot syntax in the block itself. In the example a.magic_number = 3 results in the equivalent of a[:magic_number] = 3. Secondly, the config method acts just like the instance method that you’d write if you were making a singleton class, so we don’t have to worry about multiple instances of configuration turning up.

Chain Reaction

If you’re writing a gem which performs some kind of searching on an interface, then you’re presented with somewhat of a API challenge: How can I let my users create complex searches while avoiding a bad case of the hashes? To illustrate what I mean, let’s take a look at Active Record 2 vs 3:

In the Active Record 3 example, method chaining is used instead of a potentially massive hash to define our query. This also has the added benefit of allowing us to define our query easily over time and only execute it when we’re sure we need it by calling a method that sparks off the actual query execution – each for example.

Let’s build a really simple method chaining search object:

As you can see, we’ve made a pretty convincing search interface here that feels a fair deal like Active Records 3’s query syntax. All you’ll need to do is return self at the end of chainable methods to get the effect. Notice that each is a nice extra here, as it removes the need for an explicit call to results.

Include Everything, Where Appropriate.

As I’m sure you know, you can include a module to add the module’s instance methods to a class or you can extend a module to add class methods. Often, when writing gems, we need to do both. Adding a few lines to the documentation to explain to users that we need to include Library::InstanceMethods and extend Library::ClassMethods is one approach, but that is one extra thing for the user to worry about and there’s no reason the user experience can’t be improved.

HTTParty is perhaps the most used example of this pattern.

Default HTTParty usage looks something like this:

The class Thingy will now have the very handy instance method post for POST requests, but will also have the option to call post on the class should your application not actually need to create new Thingys but just to use one.

How does this work? Let’s look at the source – copied from github with some bits chopped out for clarity:

self.included is a method defined in the Ruby core Module class which get called on when a module is included in another class. base, in this example, is the class Thingy. By overriding it, we’re now able to extend Thingy with HTTPart::ClassMethods saving the user an additional extend.

Also, because we’ve got a handle on Thingy we’re able to perform some setup on the object – as in the example here we’re some default values are set.

The included method, and it’s counterpart extended, are really powerful – but easily abused. In places where you do not need to include and extend there’s really no need to muddy the water by overriding included just so you can use include to bring in class methods – there be dragons down that road.

Be Helpful

This isn’t a code tip – just something I bumped into this week and can’t recommend enough. As developers, perhaps especially as web developers, we should all be aware that it take x seconds for a user to leave a page if they’re having a bad time. I would suspect that the same holds for programmers when they’re having a bad time setting up a gem.

Friendly exceptions are one way to be helpful. Consider replacing the exception message “No configuration file found” with “Cannot find configuration file: config/awesome.yml” – maybe you could even pop in a url to the documentation there.

Ultimately, the experience of your gem is mostly a function of code quality and usefulness. Even libraries which aren’t very user friendly can find great success in the same way as software which isn’t very user friendly can inexplicably thrive.

I’d be intrested to hear your experiances with popular gems. Is there anything that recently caught your eye as a great bit of programmer centric UI? Is there anything that any of the big gems do that’s annoying? As ever, I’ll be lurking around the comment section or on the Tweet machine – do get in touch.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Pierre-Louis Gottfrois

    Hey great article ! I was looking for one like this since a long time ago. Can’t wait to read more ;)

  • Jon

    Thanks for the article. I’m curious what your thoughts are on the proxy object approach that some people to use to achieve an ActiveRecord-like API. (e.g: https://github.com/groupme/quimby/tree/master/lib/foursquare)

  • http://rahultrikha.com Rahul Trikha

    Good work, very nicely written!

  • sabat

    Very very helpful article; sometimes I think Ruby should’ve been named Onion, because there are always more layers to peel away. Mashie alone was a great discovery (thank you!), and the config pattern makes a ton of sense.

    While (er, whilst) implementing the config pattern, I realized the code could be tightened up just a bit:

    class Awesome
    class Configuration < Hashie::Mash; end

    def self.config
    yield self.configuration if block_given?
    self.configuration
    end

    private

    def self.configuration
    @configuration ||= Configuration.new
    end
    end

    • http://www.zekefast.info/ Zeke Fast

      The code above don’t do what you probably intend. You should use `private_class_method` with symbol.

      Here is few examples that could explain why.

      1.9.3p194 :001 > class A
      1.9.3p194 :002?> def self.m
      1.9.3p194 :003?> puts ‘cool’
      1.9.3p194 :004?> end
      1.9.3p194 :005?> private
      1.9.3p194 :006?> def self.m1
      1.9.3p194 :007?> puts ‘not cool’
      1.9.3p194 :008?> end
      1.9.3p194 :009?> end
      => nil
      1.9.3p194 :010 > A.m
      cool
      => nil
      1.9.3p194 :011 > A.m1
      not cool
      => nil

      Now, better one.

      1.9.3p194 :033 > class B
      1.9.3p194 :034?> def self.m3
      1.9.3p194 :035?> puts ‘cool with m3′
      1.9.3p194 :036?> end
      1.9.3p194 :037?> private_class_method :m3
      1.9.3p194 :038?> end
      => B
      1.9.3p194 :039 > B.m3
      NoMethodError: private method `m3′ called for B:Class
      from (irb):39
      from ~/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `’

  • http://14lines.com Daniel Cooper

    @sabat Good work – that is indeed tighter.

  • http://twitter.com/pete_higgins Pete Higgins

    I’d leave out the bit with using Hashie and show how to do the same with method_missing. I appreciate it when a gem I want to add to a project omits unnecessary dependencies on things like Hashie.

  • Jon

    Pete, can you post an example without Hashie?

  • http://twitter.com/pete_higgins Pete Higgins

    Jon,

    Here’s an example using method_missing:
    https://gist.github.com/3507548

    And perhaps more in keeping with the example given, here’s an example using OpenStruct from ruby’s stdlib:
    https://gist.github.com/3507564

    These were both done quick and dirty, in a real scenario you probably wouldn’t allow setting arbitrary keys, etc.

  • http://www.cladonia.co.uk David

    Daniel

    I don’t get involved in programming though I like to keep up to date with what is possible/sitepoint news. I totally lost it in ‘O’ levels when they covered Boolean Algebra!

    However, please get something that looks like rubies in your photograph. I know rubies can come in different colours. How about this one you can use “for educational” purposes from my slide library http://photolibrary.cladonia.co.uk/media/2728f7da-3b98-11e0-a058-530c45d3e19e-ultraviolet-fluorescence-of-rubies

    Best wishes