At some point in your open source project’s lifetime users are going to want the ability to configure your software to suit their needs. They’ll want to add their own text to a dashboard. They’ll need to disable the ability to take some action. They’ll want to rip out your Taylor Swift video from the UI. FWIW I actually find the Taylor Swift embed pretty funny.
What if I told you there was a way we could all be happy? And, no, the answer doesn’t involve monkey patching. The easiest pattern I’ve come across involves introducing a
class Configuration attr_accessor :title, :text def initialize @title = "Title Default" @text = "Text Default" end end
Adding a getter to your library’s base class to retrieve a
class Library def self.configuration @configuration ||= Configuration.new end end
@configuration is memoized so the first time
configuration is called the instance variable is set and any following calls will return this reference instead of initializing a new instance each time.
Finally providing a
configure method that yields the configuration - making our instance configurable:
class Library def self.configure yield(configuration) end def self.configuration @configuration ||= Configuration.new end end
Now consumers of the library can:
Library.configure do |config| config.title = "My Custom Title" config.text = "My Custom Text" end
Any place we reference
Library.configuration.text will return the configured values. If none have been configured then our defaults in
Configuration#initialize will be returned:
<h1><%= Library.configuration.title %></h1> <p><%= Library.configuration.body %></p>
This all works for two reasons:
We memozied the configuration with
@configuration ||= Configuration.newso any configured changes applied to
@configurationwill be returned.
Classes in Ruby are just instances of
Classreferenced by a constant, which can’t be changed (That’s not entirely true, but we’ll save that explanation for another time). This means that no matter where we reference
Library.configurationwe know we’re getting the configured configuration.
Fun Fact: These two lines do the same thing. Maybe we’ll explore this in a future blog post:
class Library; end Library = Class.new
This is the exact pattern I used to implement the Flipper::UI` configuration and users seem find it pretty cool.