dnatoli
dnatoli

Reputation: 7012

Convert a hash into another hash in Ruby

I have a hash of strings

navigable_objects = { 'Dashboard' => root_path,
                      'Timesheets' => timesheets_path,
                      'Clients' => clients_path,
                      'Projects' => projects_path, 
                    }

I want to convert them into another hash where the key is again the key, but the value is either the string 'active' or empty string depending on whether the current controller name contains the key.

For example, lets say that the current controller name is "ClientsController". The result I should get is:

{ 'Dashboard' => '',
  'Timesheets' => '',
  'Clients' => 'active',
  'Projects' => ''
}

Here is how I am currently doing it:

active = {}

navigable_objects.each do |name, path|
  active[name] = (controller.controller_name.include?(name)) ? 'active' : '')
end 

I feel that while this works, there is a better way to do this in Ruby, possibly using inject or each_with_objects?

Upvotes: 5

Views: 10550

Answers (4)

Andreas Profous
Andreas Profous

Reputation: 1512

Since Ruby 2.0, there is to_h:

navigable_objects.map do |name, path|
  [name, controller.controller_name.include?(name)) ? 'active' : '']
end.to_h

I don't think it's very efficient, but for small Hashes it's an elegant way.

Upvotes: 4

Ben Lee
Ben Lee

Reputation: 53349

UPDATE: I posted another answer that I think is better for your situation. I'm leaving this one un-edited though because it has merit on its own for similar problems.

Here's the way I'd do it:

Hash[*navigable_objects.map{ |k,v| [k, controller.controller_name.include?(k) ? 'active' : ''] }.flatten]

You can run map on a hash that gets key and value pairs as input to the block. Then you can construct pairs of key/values into arrays as the output. Finally, Running Hash[*key_value_pairs.flatten] is a nice trick to turn it back into a hash. This works because you can pass an array of arguments to the Hash[] constructor to generate a hash (Hash[1, 2, 3, 4] => { 1 => 2, 3 => 4 }). And flatten turns the key value pairs into an array, and * operator turns an array into a list of arguments.

Here's a verbose version in case you want to see more clearly what's going on:

key_value_pairs = navigable_objects.map do |key, value|
    new_value = controller.controller_name.include?(k) ? 'active' : ''
    [key, new_value]
end

new_hash = Hash[*key_value_pairs.flatten]

Update: This above is compatible with ruby 1.8. As Andrew pointed out in the comments:

In 1.9 you don't need the * or flatten as Hash[] takes key-value array pairs

Upvotes: 7

Ben Lee
Ben Lee

Reputation: 53349

NOTE: I already answered but I'm posting this as a separate answer because it's better for your specific situation, but my other answer still has merit on its own.

Since you're not using the values of the hash at all, you can use each_with_object:

navigable_objects.keys.each_with_object({}) { |k,h| h[k] = controller.controller_name.include?(k) ? 'active' : '' }

Or more verbosely:

new_hash = navigable_objects.keys.each_with_object({}) do |key, hash|
   hash[key] = controller.controller_name.include?(key) ? 'active' : ''
end

If your result was based on the values too, then my other solution would work whereas this one wouldn't.

Upvotes: 5

Kyle
Kyle

Reputation: 22268

There are many many ways to accomplish this. This is just the way I'd do it.

With inject

active = navigable_objects.inject({}) do |h, obj|
  h[obj[0]] = controller.controller_name.include?(obj[0]) ? 'active' : ''
  h
end

When you call inject on a hash, the block is passed the thing you are injecting into (in this case a hash) and an array with the first element being the key and the last element being the value.

Upvotes: 1

Related Questions