Reason #146 • May 26th, 2026

Using blocks for configuration

Another common use case for blocks is configuration, which is often seen in gems as a clean and intuitive way for users to set options through code:

Ruby
module MyConfigurableGem
  class << self
    attr_accessor :option1, :option2, :option3
  end

  def self.configure
    yield(self)
  end
end

MyConfigurableGem.configure do |config|
  config.option1 = "Value for option 1"
  config.option2 = "Value for option 2"
  config.option3 = "Value for option 3"
end
    

This feels more concise than having to set each option individually with calls like MyConfigurableGem.option1 = .... It also gives the library author more control over potential side effects of setting options. For example, they could trigger some kind of initialization / refresh / validation logic when the block has finished executing.

Another way to implement this pattern is via instance_eval, which evaluates the block in the context of the gem module and lets us avoid having to pass config as an argument:

Ruby
module MyConfigurableGem
  class Configuration
    attr_accessor :option1, :option2, :option3
  end

  class << self
    attr_accessor :config

    def configure(&)
      self.config ||= Configuration.new
      instance_eval(&)
    end
  end
end

MyConfigurableGem.configure do
  config.option1 = "Value for option 1"
  config.option2 = "Value for option 2"
  config.option3 = "Value for option 3"
end
    

The latter approach is used by Rails when running Rails.application.configure, but the former is more common, likely because of its simplicity and explicitness.

Reason #147 ?