Making Your Ruby Gem Configurable
Tags: ruby, rails, beginners, coding
Published: 2021-06-06 16:43:09

Ruby gems are pretty much a module. It's not a big deal to us to declare a module, but how do we create those richest configuration files we usually have in `/initializers` folder of a Ruby on Rails application? Let's check it out.

First of all, there are many ways to make your gem configurable. I'll present you one, and further, we'll give it a look at how other gems implement it.

## Example of implementation

I'll call my example gem `gsdk`. You can assume the gem structure is the default of `bundle gem gsdk` (Bundler version 1.17.3), but this isn't important for the context of this post, it's just to give you some orientation. This gem will have a token and a secret key I'd like to inform as configuration, so let's put that in code:

```ruby
# gsdk/lib/gsdk/configuration

module Gsdk
  class Configuration
    attr_accessor :token, :secret_key

    def initialize(token = nil, secret_key = nil)
      @token = token
      @secret_key = secret_key
    end
  end
end
```

As simple as that, we have the configuration class that makes token and secret key available through `attr_accessor`.

`Gsdk` is a module, but the `Configuration` is a class instance. How do we make the instance available to be used anywhere, by any class that exists inside the `Gsdk` module?

```ruby
module Gsdk
  class << self
    attr_accessor :configuration
  end

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

It's a matter of **scope**. Although it's a module, modules are implemented as classes (`Module.class == Class`) and can make use of instance or class variables too. However, we need to inform that to the module, and we do so in a code block inside the `class << self`. The block code is enough by itself, but I wanna initialize with an empty `Configuration` instance if `configuration` is accessed, that's why we have `self.configuration`.

Now it is possible to configure the gem and make the token and secret key available in the module. 

```ruby
module Gsdk
  class Caller
    def call
      puts "Token: #{Gsdk.configuration.token}"
      puts "Secret key: #{Gsdk.configuration.secret_key}"
    end
  end
end
```

```shell
(base) ➜  gsdk git:(master) ✗ ./bin/console
2.6.6 :001 > configuration = Gsdk::Configuration.new('my_token', 'my_secret_key')
 => #<Gsdk::Configuration:0x000055b8ada3d638 @token="my_token", @secret_key="my_secret_key"> 
2.6.6 :002 > Gsdk.configuration = configuration
 => #<Gsdk::Configuration:0x000055b8ada3d638 @token="my_token", @secret_key="my_secret_key"> 
2.6.6 :003 > Gsdk::Caller.new.call
Token: my_token
Secret key: my_secret_key
 => nil 
```

The gem is already configurable, but we still missing what we proposed to answer in the first paragraph of this post: how to do what was shown above but in a configuration block. Now we know that it's a matter of scope, the answer should be easier to understand :

```ruby
module Gsdk

...

  def self.configure
    yield(configuration)
  end
end
```

The class method `self.configure` allows us to achieve our goal:

```ruby
Gsdk.configure do |config|
  config.token = "your_token"
  config.secret_key = "your_secret_key"
end
```

```shell
(base) ➜  gsdk git:(master) ✗ ./bin/console
2.6.6 :001 > Gsdk.configure do |config|
2.6.6 :002 >       config.token = "your_token"
2.6.6 :003?>     config.secret_key = "your_secret_key"
2.6.6 :004?>   end
 => "your_secret_key" 
2.6.6 :005 > Gsdk.configuration
 => #<Gsdk::Configuration:0x000055f935a2e818 @token="your_token", @secret_key="your_secret_key"> 
2.6.6 :006 > 
```

With `yield` we're calling the empty `Configuration` instance (`config.class == Gsdk::Configuration`), that uses `attr_accessor` for `token` and `secret_key`, making them available in the code block.


## Implementation alternatives


Let's check how big and consolidated gems achieve roughly the same we did above. To identify this we can check two things: (1) where is the method that accepts a configuration block, and (2) how a configuration option is set. The place to get the name of the method and the configuration options is the configuration file usually pointed out in the gem's documentation.

  - [Devise gem](https://github.com/heartcombo/devise):


```ruby
Devise.setup do |config|
...

config.password_length = 6..128

...
end
```

This is part of the configuration file generated by Devise using their `rails generate devise:install`. Looking into [`lib/devise.rb`](https://github.com/heartcombo/devise/blob/master/lib/devise.rb):

```ruby
module Devise

...

  mattr_accessor :password_length
  @@password_length = 6..128

...

  def self.setup
    yield self
  end

...
end
```

Devise uses Rails specific [mattr_accessor](https://apidock.com/rails/Module/mattr_accessor) that provides getters and setters in a class/module level, and `self.setup` is the equivalent of our `self.configure` implemented in the previous section. 

We could achieve the same as Devise eliminating the use of `Gsdk::Configuration` and changing our module to:

```ruby
module Gsdk
  class << self
    attr_accessor :token, :secret_key
  end

  def self.configure
    yield self
  end
end
```

```shell
(base) ➜  gsdk git:(master) ✗ ./bin/console
2.6.6 :001 > Gsdk.configure do |config|
2.6.6 :002 >       config.token = "your_token"
2.6.6 :003?>     config.secret_key = "your_secret_key"
2.6.6 :004?>   end
 => "your_secret_key" 
2.6.6 :005 > Gsdk.secret_key
 => "your_secret_key" 
2.6.6 :006 > Gsdk.token
 => "your_token" 
```

  - [Sidekiq gem](https://github.com/mperham/sidekiq)

```ruby
  Sidekiq.configure_server do |config|
    config.redis = { url: ENV.fetch('REDIS_URL') }
  end
```

This is part of Sidekiq configuration I have in an initializer of a Rails project. Looking into [`lib/sidekiq.rb`](https://github.com/mperham/sidekiq/blob/master/lib/sidekiq.rb):

```ruby
...

module Sidekiq

...

  def self.redis
    ...
  end

  def self.redis=(hash)
    ...
  end

  def self.configure_server
    yield self if server?
  end

...

end
```

In Sidekiq they explicitly implement getters and setters, which we saw being accomplished before using `attr_accessor` and `mattr_accessor`. Once again, we can change our implementation to work in the same manner:

```ruby
module Gsdk
  def self.token
    @token
  end

  def self.token=(token)
    @token = token
  end

  def self.secret_key
    @secret_key
  end

  def self.secret_key=(secret_key)
    @secret_key = secret_key
  end

  def self.configure
    yield self
  end
end
```

```shell
(base) ➜  gsdk git:(master) ✗ ./bin/console
2.6.6 :001 > Gsdk.configure do |config|
2.6.6 :002 >       config.token = "your_token"
2.6.6 :003?>     config.secret_key = "your_secret_key"
2.6.6 :004?>   end
 => "your_secret_key" 
2.6.6 :005 > Gsdk.token
 => "your_token" 
2.6.6 :006 > Gsdk.secret_key
 => "your_secret_key" 
```

## Conclusion

In this post, we saw how to make a gem configurable. We implemented our configuration strategy and further checked how Devise and Sidekiq gems are implemented to be configurable. 

All three implementations explored in this post are different one from another, but they summed up to getters and setters. You can explicitly implement getters and setters on your own or use something like `attr_accessor`; make use of instance variables or class variables. 

Which one is the best implementation strategy? My response would be: don't overthink this. You can argue that to use `mattr_accessor` you need an extra dependency, or that instance variables in modules are simply poor design. But the point is that we're talking about something trivial, so start with what you think is the best for you.

***

This is it! If you have any comments or suggestions, don't hold back, let me know.

***

Options if you like my content and would like to support me directly (never required, but much appreciated):

BTC address: `bc1q5l93xue3hxrrwdjxcqyjhaxfw6vz0ycdw2sg06`

[![buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1n0hai66hv33dphoiic.png)](https://www.buymeacoffee.com/rwehresmann)