Ruby Gem Configuration Patterns
brandonhilkert.com
Ruby Gem Configuration Patterns
1–10 of 13 posts
Re: Ruby Gem Configuration Patterns
#2Re: Ruby Gem Configuration Patterns
#3Re: Ruby Gem Configuration Patterns
#4For example, in Exceptionally[1], the user can add an initializer that looks like:
Exceptionally::Handler.before_render do |message, status, error, params|
# put any code here
end
In the gem, this translates to: def self.before_render(&block)
&&callback = Proc.new(&block)
end
The gem can then call the user's block and give it access to the same variables the rest of the gem does. This approach is a lot simpler than setting up an entire Middleware stack, as suggested in the blog, and makes more sense for simpler gems.Re: Ruby Gem Configuration Patterns
#5For example, compare the proposed solution:
MegaLotto.configure do |config|
config.drawing_count = 10
end
MegaLotto::Drawing.new.draw
With the alternative: MegaLotto::Drawing.new(10).draw
If you want to make it extensible, you can use keyword arguments: MegaLotto::Drawing.new(size: 10).draw
The interface and the implementation are simpler, but also the performance is better because there are less method calls. If you look at the code of both implementations, you will find the simpler one easier to understand. As a side effect, you will also get simpler stack traces if anything goes wrong.Re: Ruby Gem Configuration Patterns
#6This is actually a very complicated way of achieving the goal. It works, but it's not good from an economic point of view as it is more expensive in computational terms than a simpler OO alternative, which is to pass the size when you instantiate the MegaLotto::Drawing object. For example, compare the proposed solution: MegaLotto.configure do |config| config.drawing_count = 10 end MegaLotto::Drawing.new.draw With the…
I do agree that the constructor should have the option of passing in a Hash, which is then passed directly to the Configuration option.
Re: Ruby Gem Configuration Patterns
#7This is actually a very complicated way of achieving the goal. It works, but it's not good from an economic point of view as it is more expensive in computational terms than a simpler OO alternative, which is to pass the size when you instantiate the MegaLotto::Drawing object. For example, compare the proposed solution: MegaLotto.configure do |config| config.drawing_count = 10 end MegaLotto::Drawing.new.draw With the…
Re: Ruby Gem Configuration Patterns
#8This is actually a very complicated way of achieving the goal. It works, but it's not good from an economic point of view as it is more expensive in computational terms than a simpler OO alternative, which is to pass the size when you instantiate the MegaLotto::Drawing object. For example, compare the proposed solution: MegaLotto.configure do |config| config.drawing_count = 10 end MegaLotto::Drawing.new.draw With the…
Right...but even in this trivial gem, there may be the need to configure more than one parameter. And by having Configuration be it's own object, you can encode some validation logic at the configuration stage. I do agree that the constructor should have the option of passing in a Hash, which is then passed directly to the Configuration option.
If you have this in many different places:
MegaLotto::Drawing.new.draw
You don't have the information of how many numbers you are getting back. That's not a big deal, but adds to the cognitive load (or requires some comments). Also, if you need to draw different numbers in several different places, you will have to change the configuration many times: # First use, we need 10 numbers
MegaLotto.configure do |config|
config.drawing_count = 10
end
MegaLotto::Drawing.new.draw
# Second use, we need 6 numbers
MegaLotto.configure do |config|
config.drawing_count = 6
end
MegaLotto::Drawing.new.draw
And as soon as you do that, you may need to take multi-threading into account, because you are mutating the class.Extrapolating, it is like defining the size of an array:
Array.new(4)
If instead you configure Array.new to have a given size for all instantiations, you also lose locality and you may run into thread safety issues.In the case of MegaLotto::Drawing.new needing multiple configuration options, you can use keyword arguments. If you need too many arguments, maybe the abstraction is wrong. Even if you want to move forward with too many arguments, you can add getters/setters to the newly created instance:
# Another approach which modifies the instance
drawing = MegaLotto::Drawing.new
drawing.size = 10
drawing.draw #=> returns ten numbers
But this is not optimal design given the elements we have.Re: Ruby Gem Configuration Patterns
#9It generally means down the road I find myself wanting to use two different configurations and everything explodes in my face because the author of the gem thought there would always be exactly one configuration.
What is wrong with
MegaLotto.new do |lotto|
lotto.drawing_count = 10
end
and just wrapping it in a "default_megalotto" factory method?Re: Ruby Gem Configuration Patterns
#10Earlier quoted context omitted.
Right...but even in this trivial gem, there may be the need to configure more than one parameter. And by having Configuration be it's own object, you can encode some validation logic at the configuration stage. I do agree that the constructor should have the option of passing in a Hash, which is then passed directly to the Configuration option.
Another problem with the approach from the blog post is locality. If you need to draw 10 numbers in all places, then configuring that value in the module will work. But localy, when you look at the code, it won't tell you how many numbers you are drawing. If you have this in many different places: MegaLotto::Drawing.new.draw You don't have the information of how many numbers you are getting back. That's not a big dea…
This seems to be what the Twitter gem did too, in its newest versions. AFAIK, the Twitter configuration was a mutation to the class via config object, and now it's been revised to be thread safe:
https://github.com/sferik/twitter#configuration
client = Twitter::REST::Client.new do |config|
config.consumer_key = "YOUR_CONSUMER_KEY"
config.consumer_secret = "YOUR_CONSUMER_SECRET"
config.access_token = "YOUR_ACCESS_TOKEN"
config.access_token_secret = "YOUR_ACCESS_SECRET"
end