23tux
23tux

Reputation: 14736

Configure a class with a block

I have a base module that has some logic in it and it gets included inside various classes. Now I need a configure block that sets some configuration how the class should behave.

I tried the following code:

class MyConfig
  attr_accessor :foo, :bar
end

module BaseModule
  def self.included base
    base.include InstanceMethods
    base.extend ClassMethods
  end

  module ClassMethods
    attr_reader :config

    def configure &block
      @config = MyConfig.new
      block.call(@config)
    end
  end

  module InstanceMethods
    def do_something
      puts self.class.config.inspect
    end
  end
end

class MyClass1
  include BaseModule

  configure do |config|
    config.foo = "foo"
  end
end

class MyClass2
  include BaseModule

  configure do |config|
    config.bar = "bar"
  end
end

MyClass1.new.do_something
#<MyConfig:0x007fa052877ea0 @foo="foo">
MyClass2.new.do_something
#<MyConfig:0x007fa052877ce8 @bar="bar">
  1. I'm unsure if an instance variable @config for a module / class is the best way to configure a class. Is this the right way, or are there any better solutions?
  2. Is it good to also use base.extend ClassMethods when the module only gets included with include BaseModule? A developer could expect that only instance methods get included, but as a side effect there are also class methods extended.

Update

I've added a MyConfig class, from which a new instance is created. This has the benefit that you can use config.foo = "foo".

Upvotes: 4

Views: 1495

Answers (1)

Piotr Kruczek
Piotr Kruczek

Reputation: 2390

I've changed your code a bit to use an approach both me and a lot of popular gems (like devise) use for configuration. Hope you'll like it :)

module BaseModule
  def self.included base
    base.include InstanceMethods
    base.extend ClassMethods
  end

  module ClassMethods
    mattr_accessor :foo
    @@foo = 'initial value'

    def configure &block
      block.call(self)
    end
  end

  module InstanceMethods
    def do_something
      puts foo
    end
  end
end

class MyClass1
  include BaseModule

  configure do |klass|
    klass.foo = "foo"
  end
end

class MyClass2
  include BaseModule

  configure do |klass|
    klass.foo = "bar"
  end
end

MyClass1.new.do_something
# => "foo"
MyClass2.new.do_something
# => "bar"

The changes are:

  1. use mattr_accessor - it creates both getters and setters for your method, you can opt out some of them if you wish, more info here
  2. let configure method return self, this way you can configure your class in a simple, readable, rails-like way

Example:

Kid.config do |k|
  k.be_kind = false
  k.munch_loudly = true
  k.age = 12
end

Upvotes: 2

Related Questions