I have a Rails application with a recurring need for setting default attributes. Sometimes the user will supply values for the attributes which will be respected, but in other circumstances either the model or the user might desire that these attributes are overridden with default values disregarding original values.
I guessed that this problem called for a banged (!
) and non-banged method for setting default values allowing the user and program to switch to the appropriate state. The non-banged setter will only set default values when they are not nil whilst the banged version will always overwrite the attributes with defaults. The difference is minor:
class BangDiBang
attr_accessor :value
def set_default
self.value ||= do_some_suff_to_determine_default_value
end
def set_default!
self.value = do_some_suff_to_determine_default_value
end
...
end
The issue with this code is that if I had a bunch of variables to set, I would end up repeating the same code twice for each variable.
My question is how to partial out this code? Saving the logic in one method and having two methods set_value
and set_value!
calling the central logic with the different assignment operators.
I have conjured one solution: write the central logic as text, replace the assignment operation from the setter methods and evaluate (but this does not feel right). How do I not repeat myself?
The way you're going about this isn't going to work for multiple params since you're calling set_default
but not specifying which variable. You'll want to define behavior for each variable, ideally. There are libraries that handle this sort of thing, but you can roll your own pretty easily:
class Example
def self.default_param(name, default_value)
define_method("default_#{name}") { default_value }
define_method(name) do
instance_variable_get("@#{name}") || default_value
end
attr_writer name
end
default_param :foo, 'foo default'
end
ex = Example.new
ex.foo #=> "foo default"
ex.foo = 'bar'
ex.foo #=> "bar"
ex.default_foo #=> "foo default"
I've renamed set_default
and set_default!
to be more clear: for each variable with a default value, three methods are created (example using foo
as the variable name):
foo
— returns the value of @foo
if it is truthy, and default_foo
otherwisefoo=
— sets the value of @foo
default_foo
— returns the specified defaultYou could compartmentalize and dry up some of the code above further, creating a default_params
(plural) method to take a hash, extracting the class macro to a concern:
module DefaultParams
def default_param(name, default_value)
define_method("default_#{name}") { default_value }
define_method(name) do
instance_variable_get("@#{name}") || default_value
end
attr_writer name
end
def default_params(params)
params.each { |default| default_param(*default) }
end
end
class Example
extend DefaultParams
default_params foo: 'default foo', bar: 'my favorite bar'
end