

Ruby Gem Configuration Patterns - frist45
http://brandonhilkert.com/blog/ruby-gem-configuration-patterns/?utm_source=hn&utm_medium=social&utm_campaign=gem-config

======
feca
This is actually a very complicated way of achieving the goal. It works, but
it's not good from an economic point of view as it is more expensive in
computational terms than a simpler OO alternative, which is to pass the size
when you instantiate the MegaLotto::Drawing object.

For example, compare the proposed solution:

    
    
        MegaLotto.configure do |config|
          config.drawing_count = 10
        end
    
        MegaLotto::Drawing.new.draw
    

With the alternative:

    
    
        MegaLotto::Drawing.new(10).draw
    

If you want to make it extensible, you can use keyword arguments:

    
    
        MegaLotto::Drawing.new(size: 10).draw
    

The interface and the implementation are simpler, but also the performance is
better because there are less method calls. If you look at the code of both
implementations, you will find the simpler one easier to understand. As a side
effect, you will also get simpler stack traces if anything goes wrong.

~~~
danso
Right...but even in this trivial gem, there may be the need to configure more
than one parameter. And by having Configuration be it's own object, you can
encode some validation logic at the configuration stage.

I do agree that the constructor should have the option of passing in a Hash,
which is then passed directly to the Configuration option.

~~~
feca
Another problem with the approach from the blog post is locality. If you need
to draw 10 numbers in all places, then configuring that value in the module
will work. But localy, when you look at the code, it won't tell you how many
numbers you are drawing.

If you have this in many different places:

    
    
        MegaLotto::Drawing.new.draw
    

You don't have the information of how many numbers you are getting back.
That's not a big deal, but adds to the cognitive load (or requires some
comments). Also, if you need to draw different numbers in several different
places, you will have to change the configuration many times:

    
    
        # First use, we need 10 numbers
        MegaLotto.configure do |config|
          config.drawing_count = 10
        end
    
        MegaLotto::Drawing.new.draw
    
        # Second use, we need 6 numbers
        MegaLotto.configure do |config|
          config.drawing_count = 6
        end
    
        MegaLotto::Drawing.new.draw
    

And as soon as you do that, you may need to take multi-threading into account,
because you are mutating the class.

Extrapolating, it is like defining the size of an array:

    
    
        Array.new(4)
    

If instead you configure Array.new to have a given size for all
instantiations, you also lose locality and you may run into thread safety
issues.

In the case of MegaLotto::Drawing.new needing multiple configuration options,
you can use keyword arguments. If you need too many arguments, maybe the
abstraction is wrong. Even if you want to move forward with too many
arguments, you can add getters/setters to the newly created instance:

    
    
       # Another approach which modifies the instance
       drawing = MegaLotto::Drawing.new
       drawing.size = 10
       drawing.draw #=> returns ten numbers
    

But this is not optimal design given the elements we have.

~~~
danso
Ah yes, mutating the class is most definitely a concern...which is why I give
you an additional +1 for advocating for a Hash to be passed into the
constructor.

This seems to be what the Twitter gem did too, in its newest versions. AFAIK,
the Twitter configuration was a mutation to the class via config object, and
now it's been revised to be thread safe:

[https://github.com/sferik/twitter#configuration](https://github.com/sferik/twitter#configuration)

    
    
        client = Twitter::REST::Client.new do |config|
          config.consumer_key        = "YOUR_CONSUMER_KEY"
          config.consumer_secret     = "YOUR_CONSUMER_SECRET"
          config.access_token        = "YOUR_ACCESS_TOKEN"
          config.access_token_secret = "YOUR_ACCESS_SECRET"
        end

------
riffraff
I wish people would stop doing this.

It generally means down the road I find myself wanting to use two different
configurations and everything explodes in my face because the author of the
gem thought there would always be exactly one configuration.

What is wrong with

    
    
         MegaLotto.new do |lotto|
           lotto.drawing_count = 10
         end
    

and just wrapping it in a "default_megalotto" factory method?

------
guptaneil
This is a great approach for setting config variables. I also like to enable
the user to add custom functionality to the gem by accepting a block that is
called with any relevant variables for the user to access.

For example, in Exceptionally[1], the user can add an initializer that looks
like:

    
    
        Exceptionally::Handler.before_render do |message, status, error, params|
          # put any code here
        end
    

In the gem, this translates to:

    
    
        def self.before_render(&block)
          &&callback = Proc.new(&block)
        end
    

The gem can then call the user's block and give it access to the same
variables the rest of the gem does. This approach is a lot simpler than
setting up an entire Middleware stack, as suggested in the blog, and makes
more sense for simpler gems.

1:
[https://github.com/neilgupta/exceptionally](https://github.com/neilgupta/exceptionally)

------
muaddirac
This is a good overview of a config pattern. I'd personally have preferred it
without the TDD stuff, seems to get in the way of the explanation.

------
geeio
Check out my configuration gem.

[https://github.com/GeorgeErickson/bonfig/](https://github.com/GeorgeErickson/bonfig/)

