Idiomatic way to merge "multivalued" options

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

Michael Schuerig wrote:

I'm looking for a nice and idiomatic way to merge options hashes.

The Ruby newsgroup deserves to work on questions like this.

news:comp.lang.ruby

Doesn't it depend on which direction you decide to conduct the merge? E.g.:

existing_options.merge(new_options)

will overwrite existing keys (which is normally what you want if existing options specify defaults). However:

new_options.merge(existing_options)

will cause any keys present in new_options to be overwritten by those in existing_options (if they exist).

I don't know if this answers the question, but I hope it helps...

Michael Schuerig wrote: