Flattening hash into string in Ruby

Solution 1:

I wouldn't override .flatten, which is already defined:

Returns a new array that is a one-dimensional flattening of this hash. That is, for every key or value that is an array, extract its elements into the new array. Unlike Array#flatten, this method does not flatten recursively by default. If the optional level argument determines the level of recursion to flatten.

This is simplest way to do it that I'm aware of:

{:a => 100, :b => 200}.map{|k,v| "#{k}=#{v}"}.join('&')
# => "a=100&b=200"

Solution 2:

Slight variation of @elektronaut's version:

You can actually put just an |e| there instead of |k, v| in which case e is an array of two elements and you can call e.join('='). Altogether you have something like

class Hash
  def join(keyvaldelim=$,, entrydelim=$,) # $, is the global default delimiter
    map {|e| e.join(keyvaldelim) }.join(entrydelim)
  end
end

{a: 100, b: 200}.join('=', '&') # I love showing off the new Ruby 1.9 Hash syntax
# => 'a=100&b=200'

Solution 3:

If you're trying to generate a url query string, you most certainly should use a method like activesupport's to_param (aliased to to_query). Imagine the problems if you had an ampersand or equal sign in the data itself.

to_query takes care of escaping:

>> require 'active_support/core_ext/object'
>> {'a&' => 'b', 'c' => 'd'}.to_query
>> => "a%26=b&c=d"

EDIT

@fahadsadah makes a good point about not wanting to load Rails. Even active_support/core_ext/object will load 71 files. It also monkey-patches core classes.

A lightweight and cleaner solution:

require 'rack'  # only loads 3 files
{'a&' => 'b', 'c' => 'd'}.map{|pair|
  pair.map{|e| Rack::Utils.escape(e.to_s) }.join('=')
}.join('&')
# => "a%26=b&c=d"

It's important to escape, otherwise the operation isn't reversible.

Solution 4:

Well, you could do it with standard methods and arrays:

class Hash
  def flatten(keyvaldelimiter, entrydelimiter)
    self.map { |k, v| "#{k}#{keyvaldelimiter}#{v}" }.join(entrydelimiter)
  end
end

You might also be interested in the Rails to_query method.

Also, obviously, you can write "#{k}#{keyvaldelimiter}#{v}" as k.to_s + keyvaldelimiter + v.to_s...

Solution 5:

Not sure if there's a built-in way, but here's some shorter code:

class Hash
  def flatten(kvdelim='', entrydelim='')
    self.inject([]) { |a, b| a << b.join(kvdelim) }.join(entrydelim)
  end
end

puts ({ :a => :b, :c => :d }).flatten('=', '&') # => a=b&c=d