I'm looking for a nice and idiomatic way to merge options hashes. The usual ways for scalar-valued options are
def my_method(options = {}) options = { :foo => 'something', :bar => 'else' }.update(options) ... end
and
def my_method(options = {}) options = options.reverse_merge( :foo => 'something', :bar => 'else' ) ... end
The cases I'm interested in are different. For illustration, let's take a helper method that inserts a text field into the page and adds the class attribute "special". In particular, it should not overwrite existing class attribute options, only add a new one.
def my_special_text_field1(object, field, options = {}) if options[:class] options[:class] += ' special' else options[:class] = 'special' end text_field(object, field, options) end
That's rather clumsy. How about this, then
def my_special_text_field2(object, field, options = {}) options[:class] = (String(options[:class]).split(/\s+/) | ['special']) * ' ' text_field(object, field, options) end
Ugh. Well, it has the added advantage that duplicates are not added. Alas, the code is not very obvious. But with a bit of encapsulation and class opening that can be rectified.
def my_special_text_field3(object, field, options = {}) text_field(object, field, options.merge_multivalued(:class => 'special')) end
class Hash def merge_multivalued(other_hash) merge_arrays(other_hash) do |old_value, other_array| (String(old_value).split(/\s+/) | other_array) * ' ' end end
private
def merge_arrays(other_hash, &block) # add values from other_hash to already present values merged_old = inject({}) do |memo, (key, value)| if other_value = other_hash[key] other_array = other_value.kind_of?(Array) ? other_value : other_value.split(/\s+/) memo[key] = block[value, other_array] else memo[key] = value end memo end # add new values from other_hash other_hash.merge(merged_old) end end
That does look a bit over-engineered. Here's a minimalist (and slightly rogueish) alternative.
def my_special_text_field4(object, field, options = {}) options[:class] = (Array(options[:class]) | ['special']) * ' ' text_field(object, field, options) end
After all this, I still can't say that I'm fully satisfied with any of the options. I'm looking forward to suggestions.
Michael