In Ruby On Rails By Leigh Halliday November 24, 2014 Leigh Halliday

Block Based Configuration

Block based configuration is a pattern you see quite a bit when using ruby. When I look at this website itself, there are 3 different gems that I am configuring using this approach. I think it provides a clean and encapsulated interface for configuration, and it is actually quite easy for you to use yourself in your own gems or projects.

Usage Examples

In the simple form gem, used for building forms, here is an example of its configuration.

SimpleForm.setup do |config|
  # Default tag used for error notification helper.
  config.error_notification_tag = :div

  # CSS class to add for error notification helper.
  config.error_notification_class = 'alert alert-error'
end

In the sorcery gem, used for authentication, here is what its configuration looks like. This one actually has a sub configuration inside of the main one.

Rails.application.config.sorcery.configure do |config|
  # --- user config ---
  config.user_config do |user|
    user.reset_password_email_method_name = :reset_password_email
  end

  # This line must come after the 'user config' block.
  # Define which model authenticates with sorcery.
  config.user_class = "User"
end

Example

Here's a simple example for configuring the settings of the Idioma gem. I'll include all of the code here at the beginning, but I'll break down some of the choices and how to use it below.

module Idioma

  class Configuration
    attr_writer :locales, :default_locale
    attr_accessor :redis_backend

    def initialize
      self.default_locale = :en
      self.locales = [self.default_locale]
    end

    def locales
      proc_or_value(@locales)
    end

    def default_locale
      proc_or_value(@default_locale)
    end

    private

    def proc_or_value(var)
      var.is_a?(Proc) ? var.call : var
    end

  end

  # Return a single instance of the Configuration class
  # @return [Idioma::Configuration] single instance
  def self.conf
    @configuration ||= Configuration.new
  end

  # Configure the settings for this module.
  # @param [lambda] which will be passed instance of Configuration class
  def self.configure
    yield(conf)
  end

end

Usage

We usually set this up in a file called idioma.rb inside of the config/initializers folder. One thing to keep in mind is that this code is only run once which happens when the project is initialized. Because I am pulling the setting values in this case from the database, I've chosen to send a lambda as the value instead of a straight value so that it will be evaluated every time the setting is needed. This is so when the DB value changes, it is reflected in the app immediately instead of needing to restart the application.

# config/initializers/idioma.rb
Idioma.configure do |configure|
  configure.redis_backend = I18n.backend

  configure.default_locale = -> {
    Setting.get(:i18n_default_locale).to_sym
  }

  configure.locales = -> {
    Setting.get(:i18n_available_locales).split(",").map { |locale| locale.strip.to_sym }
  }
end

Explanation

This method is what handles receiving a lambda or a simple value. It checks to see if it is a Proc, and if so will call it, otherwise it just returns the value.

def proc_or_value(var)
  var.is_a?(Proc) ? var.call : var
end

This method returns a memoized instance of the Configuration class... it treats it as a singleton because we don't need multiple instances of it.

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

This is the method which is called when actually doing the configuration. It expects a block and an instance of the Configuration class is passed to it.

def self.configure
  yield(conf)
end

Lastly, if you want to just grab a setting from the Idioma module somewhere in your code, say, which locales are available to it, you would grab it like this.

available_locales = Idioma.conf.locales