Ruby API development: module/class structure to set instance variables via do/block

In this blog post I'll show an example Ruby module/class structure to allow users to set configuration settings via a do/block, like:

Api.config do |c|

  c.first_name = 'Eric'
  c.last_name = 'London'

end

The following code shows how a simple module/class structure could be defined to allow end-users to set instance variables on a module's singleton class:

#!/usr/bin/env ruby

module Api

  # mixes in module methods as class methods
  extend self

  def config

    if block_given?

      # pass block to settings singleton
      yield Settings

    else

      # return instance variables
      vars = {}
      Settings.instance_variables.each do |key|
        vars[key] = Settings.instance_variable_get key
      end
      vars

    end

  end

end

module Api

  class Settings

    # dynamic method call
    def self.method_missing(method, *args, &block)

      # check for assignment methods
      if method.to_s =~ /=$/
        key = method.to_s.gsub(/=$/,'')
        set_var(key, args.first)
      else
        super
      end

    end

    # or, an explicit method list:
    # def self.first_name=(value)
    #   # blah
    # end

    class << self

      private

      def set_var(key, value)
        self.instance_variable_set "@#{key}".to_s, value
      end

    end

  end

end

Usage and output:

Api.config do |c|

  c.first_name = 'Eric'
  c.last_name = 'London'

end

require 'pp'
pp Api.config
{:@first_name=>"Eric", :@last_name=>"London"}

Update 2013-10-20:

Below is another example that allows you to call methods on the singleton class, instead of using the assignment operator with a block parameter; as such:

Api.config do
  first_name 'Eric'
  last_name 'London'
end

Example code:

#!/usr/bin/env ruby

module Api

  # mixes in module methods as class methods
  extend self

  def config(*args, &block)

    if block_given?
      return Settings.instance_exec &block

    else

      # return instance variables
      vars = {}
      Settings.instance_variables.each do |key|
        vars[key] = Settings.instance_variable_get key
      end
      vars

    end

  end

end

module Api

  class Settings

    # dynamic method call
    def self.method_missing(method, *args, &block)

      if [
        :first_name,
        :last_name,
      ].include? method
        set_var method, args.first
      else
        super
      end

    end

    class << self

      private

      def set_var(key, value)
        self.instance_variable_set "@#{key}".to_s, value
      end

    end

  end

end

Usage and output:

Api.config do
  first_name 'Eric'
  last_name 'London'
end

require 'pp'
pp Api.config
{:@first_name=>"Eric", :@last_name=>"London"}