Ruby constants are a nice place to put application configuration information, but they can be inflexible if you want to defer initialization until later — for example, if you want a constant to have a given value at application startup only if a certain environment variable or command–line parameter is set. I like the idea of the single–assignment variables that you get in many functional and logic languages, and I also like the idea that constants only really need to be constant after they’ve been read once. (See this paper for some interesting implications of that idea in Java programs.)
The quiescent gem is a little hack I put together to make it easy to have write-once, defaultable constants in your Ruby programs. A quiescing constant, or quiescent, has an optional default value (either an explicit value or a block to calculate that value), and can be assigned to at most once in a program execution. Once the quiescent is written to, its value quiesces and it becomes a normal constant. If it is read without being explicitly quiesced, it assumes the default value (or the result of executing the default-value block) and becomes a normal constant. Here’s an example to make things clearer:
require 'quiescent'
class Foo
include Quiescent
# This declares a constant named PostCode that
# quiesces to a default value of 53706 unless
# another is provided before the first time it
# is read
:PostCode, 53706
quiescent
# Let's assume that the awesome features are off
# by default.
:EnableTotallyAwesomeFeature, false
quiescent :EnableSlightlyLessAwesomeFeature, false
quiescent
# This declares a constant named LazyThrees that
# quiesces to a list of all natural numbers less than
# 100 that are divisible by three, as calculated
# in the block, unless another value is provided.
# The block argument will execute at most once.
:LazyThrees do
quiescent 1..100).to_a.select {|x| x % 3 == 0}
(end
# In this method, we'll see how to force quiescents
# to quiesce by giving them values and reading their
# values.
def self.setup
# We only want to do this once
return if @setup_done
@setup_done = true
puts "The postal code is #{Foo::PostCode}"
# You can provide non-default values with the
# quiesce method...
Foo.quiesce(:EnableTotallyAwesomeFeature, "sometimes")
# ...or by using a special CONSTNAME= method, which
# will be intercepted by method_missing.
Foo.EnableSlightlyLessAwesomeFeature = true
# Note that this only works for names corresponding
# to declared quiescing constants...
begin
Foo.EnableCrummyFeature = true
rescue Exception
puts("whoa, failure in aisle 47")
end
# ...and only once for each quiescing constant.
begin
Foo.EnableSlightlyLessAwesomeFeature = false
rescue Exception
puts("nice try, pal")
end
end
end
Get quiescent from GitHub or from RubyGems.