Reputation: 3900
I'm having a go at writing my first gem, but am a bit confused as to how the typical configuration blocks work. To illustrate, what I want to have is the facility to write something like
MyGem.configure do |c|
c.property1 = 1
c.property2 = 'some string'
end
My ruby knowledge is less than deep, so while I'm happy using blocks, I'm not sure how to write code that expects a block.
How do I write my "MyGem" class to be configured (presumably via an instance variable) in this way?
Upvotes: 4
Views: 163
Reputation: 31726
This would be the likely way to implement your example:
class MyGem
Config = Struct.new :property1, :property2 # => MyGem::Config
def self.configure(&config_block)
config_block.call config # => "some string"
end # => :configure
def self.config
@config ||= Config.new # => #<struct MyGem::Config property1=nil, property2=nil>, #<struct MyGem::Config property1=1, property2="some string">
end # => :config
end # => :config
MyGem.configure do |c| # => MyGem
c.property1 = 1 # => 1
c.property2 = 'some string' # => "some string"
end # => "some string"
MyGem.config # => #<struct MyGem::Config property1=1, property2="some string">
But I'll also advocate storing state on the instance rather than the class:
class MyGem
Config = Struct.new :property1, :property2 # => MyGem::Config
def initialize(&config_block)
config_block.call config # => "some string"
end # => :initialize
def config
@config ||= Config.new # => #<struct MyGem::Config property1=nil, property2=nil>, #<struct MyGem::Config property1=1, property2="some string">
end # => :config
end # => :config
instance = MyGem.new do |c| # => MyGem
c.property1 = 1 # => 1
c.property2 = 'some string' # => "some string"
end # => #<MyGem:0x007fc1691ecb20 @config=#<struct MyGem::Config property1=1, property2="some string">>
instance.config # => #<struct MyGem::Config property1=1, property2="some string">
If you do it this way, you don't have global state. Notice we could have multiple different configurations (e.g. each client has their own config, or you want to write more than one test, but now each one risks breaking the others). I wrote a blog about many ways I'd thought of to implement the singleton pattern, and why you shouldn't do it: http://blog.8thlight.com/josh-cheek/2012/10/20/implementing-and-testing-the-singleton-pattern-in-ruby.html
Upvotes: 5