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: