How to deep transform values on Ruby hash - ruby

I have a hash which looks like this:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
I need to convert each value which is a single string inside an array so that it ends up like this:
hash = {
'key1' => 'value' ,
'key2' => {
'sub1' => 'string' ,
'sub2' => 'string' ,
},
'shippingInfo' => {
'shippingType' => 'Calculated' ,
'shipToLocations' => 'Worldwide' ,
'expeditedShipping' => 'false' ,
'oneDayShippingAvailable' => 'false' ,
'handlingTime' => '3' ,
}
}
I found this but couldn't get it work
https://gist.github.com/chris/b4138603a8fe17e073c6bc073eb17785

What about something like:
def deep_transform_values(hash)
return hash unless hash.is_a?(Hash)
hash.transform_values do |val|
if val.is_a?(Array) && val.length == 1
val.first
else
deep_transform_values(val)
end
end
end
Tested with something like:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
'an_integer' => 1,
'an_empty_array' => [],
'an_array_with_more_than_one_elements' => [1,2],
'a_symbol' => :symbol,
'a_string' => 'string'
}
}
Gives:
{
"key1"=>"value",
"key2"=>{
"sub1"=>"string",
"sub2"=>"string"
},
"shippingInfo"=> {
"shippingType"=>"Calculated",
"shipToLocations"=>"Worldwide",
"expeditedShipping"=>"false",
"oneDayShippingAvailable"=>"false",
"handlingTime"=>"3",
"an_integer"=>1,
"an_empty_array"=>[],
"an_array_with_more_than_one_elements"=>[1, 2],
"a_symbol"=>:symbol,
"a_string"=>"string"
}
}
Following your question in the comments, I guess the logic would change a bit:
class Hash
def deep_transform_values
self.transform_values do |val|
next(val.first) if val.is_a?(Array) && val.length == 1
next(val) unless val.respond_to?(:deep_transform_values)
val.deep_transform_values
end
end
end

hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide', 'Web'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
def recurse(hash)
hash.transform_values do |v|
case v
when Array
v.size == 1 ? v.first : v
when Hash
recurse v
else
# raise exception
end
end
end
recurse hash
#=> {"key1"=>"value",
# "key2"=>{
# "sub1"=>"string",
# "sub2"=>"string"
# },
# "shippingInfo"=>{
# "shippingType"=>"Calculated",
# "shipToLocations"=>["Worldwide", "Web"],
# "expeditedShipping"=>"false",
# "oneDayShippingAvailable"=>"false",
# "handlingTime"=>"3"
# }
# }

As an alternative, consider using an object and allowing the initializer to deconstruct some of the keys for you.
One of the reasons a lot of people like myself started using Ruby in favour of Perl was because of the better expression of objects in place of primitives like arrays and hashes. Use it to your advantage!
class ShippingStuff # You've kept the data vague
def initialize key1:, key2:, shippingInfo:
#blk = -> val {
val.respond_to?(:push) && val.size == 1 ?
val.first :
cleankeys(val)
}
#key1 = cleankeys key1
#key2 = cleankeys key2
#shippingInfo = shippingInfo
end
attr_reader :key1, :key2, :shippingInfo
# basically a cut down version of what
# Sebastian Palma answered with
def cleankeys data
if data.respond_to? :transform_values
data.transform_values &#blk
else
#blk.call(data)
end
end
end
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
shipper = ShippingStuff.new hash.transform_keys!(&:to_sym)
shipper.key1
# "value"
shipper.key2
# {"sub1"=>"string", "sub2"=>"string"}
shipper.shippingInfo
# {"shippingType"=>["Calculated"], "shipToLocations"=>["Worldwide"], "expeditedShipping"=>["false"], "oneDayShippingAvailable"=>["false"], "handlingTime"=>["3"]}
In the same vein, I'd even make an Info class for the shippingInfo data.
You may run into a different problem if key1 and key2 are dynamic, but there's ways around that too (double splat for one).

I noticed a lot of answers with unnecessary recursion. Current version of Ruby 2.7.x with ActiveSupport (I tested with 6.1.4.4) will allow you to do this:
Input data:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string']},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide', 'Web'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3']}}
Solution:
hash.deep_transform_values do |value|
# whatever you need to do to any nested value, like:
if value == value.to_i.to_s
value.to_i
else
value
end
end
The example above will return a typecast String to Integer.

Related

Accessing a hash element that has a value of false specificially

I have a hash that i use in watir automation and it returns a true/false value based on the presence of elements in a UI. Rather than returning the entire hash, can i just return anything that evaluates to false?
elements = {
"Title" => #b.title == 'Details',
"Name" => #b.div(:class => 'rpt-name').present?,
"Address" => #b.div(:class => 'rpt-address').present?,
"Stats" => #b.div(:class => 'rpt-stats-container-top').present?,
"Employee Information" => #b.div(:class => 'rpt-stats-container').present?,
"Reports" => #b.div(:class => 'rpt-ribbon-container').present?
}
if elements.values.include? false
puts "ERROR: Page Validation Failed. #{elements.inspect}"
valid = false
else
valid = true
end
valid
you can use select and return all values that are false
elements.select {|_key, value| !value }
your code would be like:
elements = {
"Title" => #b.title == 'Details',
"Name" => #b.div(:class => 'rpt-name').present?,
"Address" => #b.div(:class => 'rpt-address').present?,
"Stats" => #b.div(:class => 'rpt-stats-container-top').present?,
"Employee Information" => #b.div(:class => 'rpt-stats-container').present?,
"Reports" => #b.div(:class => 'rpt-ribbon-container').present?
}.select { |_key, value| !value }

Ruby comparing values in hashes and returning the same structure

I have 2 hashes like so:
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
...
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
...
:different_x => "some_x_value"
}
}
I find the common values in the :values key of both the hashes like this :
same_values = hash[:values].values & compared_hash[:values].values
And once I get it, I want to return a new hash similar to 'stored_hash', but with the :values of it containing the same_values I found before.
For example, if both hashes have "some_value", "some_other_value", "a_new_value", my new hash should look like :
new_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value"
}
}
This should work for you :
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_a_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
:different_x => "some_b_value"
}
}
common_values = compare_hash[:values].values & stored_hash[:values].values
new_hash = stored_hash.dup
new_hash[:values] = {}
common_values.each_with_index do |value, index |
new_hash[:values]["value_#{index+1}".to_sym] = value
end
new_hash
# => new_hash = {
# :name => "hash_1",
# :version => "1.0",
# :values => {
# :value_1 => "some_value",
# :value_2 => "some_other_value",
# :value_3 => "a_new_value"
# }
# }
This is one way you could obtain your desired result.
Code
require 'set'
def make_new_hash(stored_hash, compare_hash)
new_hash = stored_hash.dup
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
values_hash = new_hash[:values]
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
new_hash
end
Example
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "a_new_value",
:different_3 => "some_strange_value",
:different_x => "some_x_value"
}
}
Note that I've made a small change in stored_hash from that given in the question.
make_new_hash(stored_hash, compare_hash)
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Explanation
Create a copy of stored_hash:
new_hash = stored_hash.dup
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value",
# :value_2=>"some_other_value",
# :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
We only want to keep a key k of new_hash[:values] if new_hash[:values][k] is one of the values in both the hash stored_hash[:values] and the hash compare_hash[:values], so we obtain those values:
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
# => #<Set: {"some_value", "a_new_value", "some_x_value"}>
I chose to save them in a set both to speed lookup and to obtain unique values. They could alternatively be saved to array like so:
(stored_hash[:values].values & compare_hash[:values].values).uniq
#=> ["some_value", "a_new_value", "some_x_value"]
The code that follows is the same if an array is used rather than a set.
To simpify, let's create a variable:
values_hash = new_hash[:values]
#=> {:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}
Next determine the keys of New_hash[:values] we wish to keep:
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
#=> [:value_1, :value_3, :value_x]
All keys other than :value_2 are kept. That key is not kept because comparative_hash[:values] does not have a value "some_other_value".
We can now construct the updated value of new_hash[:values]:
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
#=> {:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}
Lastly, we return new_hash:
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Let's confirm that stored_hash will not be changed when a value of new_hash[:values] is changed:
new_hash[:values][:value_1] = 'cat'
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"cat", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
stored_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}}

Understanding attributes in AWS DynamoDB with Ruby

I can't seem to wrap my head around the AWS Ruby SDK documentation for DynamoDB (or more specifically the concepts of the DynamoDB data model).
Specifically I've been reading: http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html#!AWS/DynamoDB.html
Note: I have read through the Data Model documentation as well and it's still not sinking in; I'm hoping a proper example in Ruby with clear up my confusion
In the following code snippet, I create a table called "my_books" which has a primary_key called "item_id" and it's a Hash key (not a Hash/Range combination)...
dyn = AWS::DynamoDB::Client::V20120810.new
# => #<AWS::DynamoDB::Client::V20120810>
dyn.create_table({
:attribute_definitions => [
{ :attribute_name => "item_id", :attribute_type => "N" }
],
:table_name => "my_books",
:key_schema => [
{ :attribute_name => "item_id", :key_type => "HASH" },
],
:provisioned_throughput => {
:read_capacity_units => 10,
:write_capacity_units => 10
}
})
# => {:table_description=>{:attribute_definitions=>[{:attribute_name=>"item_id", :attribute_type=>"N"}], :table_name=>"my_books", :key_schema=>[{:attribute_name=>"item_id", :key_type=>"HASH"}], :table_status=>"ACTIVE", :creation_date_time=>2014-11-24 16:59:47 +0000, :provisioned_throughput=>{:number_of_decreases_today=>0, :read_capacity_units=>10, :write_capacity_units=>10}, :table_size_bytes=>0, :item_count=>0}}
dyn.list_tables
# => {:table_names=>["my_books"]}
dyn.scan :table_name => "my_books"
# => {:member=>[], :count=>0, :scanned_count=>0}
I then try and populate the table with a new item. My understanding is that I should specify the numerical value for item_id (which is the primary key) and then I could specify other attributes for the new item/record/document I'm adding to the table...
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => 1,
"item_title" => "My Book Title",
"item_released" => false
}
)
But that last command returns the following error:
expected hash value for value at key item_id of option item
So although I don't quite understand what the hash will be made of, I try doing that:
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => 1 },
"item_title" => "My Book Title",
"item_released" => false
}
)
But this now returns the following error...
expected string value for key N of value at key item_id of option item
I've tried different variations, but can't seem to figure out how this works?
EDIT/UPDATE: as suggested by Uri Agassi - I changed the value from 1 to "1". I'm not really sure why this has to be quoted as I've defined the type to be a number and not a string, but OK let's just accept this and move on.
I've finally figured out most of what I needed to understand the data model of DynamoDB and using the Ruby SDK.
Below is my example code, which hopefully will help someone else, and I've got a fully fleshed out example here: https://gist.github.com/Integralist/9f9f2215e001b15ac492#file-3-dynamodb-irb-session-rb
# https://github.com/BBC-News/alephant-harness can automate the below set-up when using Spurious
# API Documentation http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html
# Ruby SDK API Documentation http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html#!AWS/DynamoDB/Client/V20120810.html
require "aws-sdk"
require "dotenv"
require "spurious/ruby/awssdk/helper"
Spurious::Ruby::Awssdk::Helper.configure
# => <AWS::Core::Configuration>
Dotenv.load(
File.join(
File.dirname(__FILE__), "config", "development", "env.yaml"
)
)
# => {"AWS_REGION"=>"eu-west-1", "AWS_ACCESS_KEY_ID"=>"development_access", "AWS_SECRET_ACCESS_KEY"=>"development_secret", "DYNAMO_LU"=>"development_lookup", "DYNAMO_SQ"=>"development_sequence", "SQS_QUEUE"=>"development_queue", "S3_BUCKET"=>"development_bucket"}
dyn = AWS::DynamoDB::Client.new :api_version => "2012-08-10"
dyn = AWS::DynamoDB::Client::V20120810.new
# => #<AWS::DynamoDB::Client::V20120810>
dyn.create_table({
# This section requires us to define our primary key
# Which will be called "item_id" and it must be a numerical value
:attribute_definitions => [
{ :attribute_name => "item_id", :attribute_type => "N" }
],
:table_name => "my_books",
# The primary key will be a simple Hash key (not a Hash/Range which requires both key types to be provided)
# The attributes defined above must be included in the :key_schema Array
:key_schema => [
{ :attribute_name => "item_id", :key_type => "HASH" }
],
:provisioned_throughput => {
:read_capacity_units => 10,
:write_capacity_units => 10
}
})
# => {:table_description=>{:attribute_definitions=>[{:attribute_name=>"item_id", :attribute_type=>"N"}], :table_name=>"my_books", :key_schema=>[{:attribute_name=>"item_id", :key_type=>"HASH"}], :table_status=>"ACTIVE", :creation_date_time=>2014-11-24 16:59:47 +0000, :provisioned_throughput=>{:number_of_decreases_today=>0, :read_capacity_units=>10, :write_capacity_units=>10}, :table_size_bytes=>0, :item_count=>0}}
dyn.list_tables
# => {:table_names=>["my_books"]}
dyn.scan :table_name => "my_books"
# => {:member=>[], :count=>0, :scanned_count=>0}
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => "1" }, # oddly this needs to be a String and not a strict Integer?
"item_title" => { "S" => "My Book Title"},
"item_released" => { "B" => "false" }
}
)
# Note: if you use an "item_id" that already exists, then the item will be updated.
# Unless you use the "expected" conditional feature
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => "1" }, # oddly this needs to be a String and not a strict Integer?
"item_title" => { "S" => "My Book Title"},
"item_released" => { "B" => "false" }
},
# The :expected key specifies the conditions of our "put" operation.
# If "item_id" isn't NULL (i.e. it exists) then our condition has failed.
# This means we only write the value when the key "item_id" hasn't been set.
:expected => {
"item_id" => { :comparison_operator => "NULL" }
}
)
# AWS::DynamoDB::Errors::ConditionalCheckFailedException: The conditional check failed
dyn.scan :table_name => "my_books"
# => {:member=>[{"item_id"=>{:n=>"1"}, "item_title"=>{:s=>"My Book Title"}, "item_released"=>{:b=>"false"}}], :count=>1, :scanned_count=>1}
dyn.query :table_name => "my_books", :consistent_read => true, :key_conditions => {
"item_id" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "n" => "1" }]
},
"item_title" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "s" => "My Book Title" }]
}
}
# => {:member=>[{"item_id"=>{:n=>"1"}, "item_title"=>{:s=>"My Book Title"}, "item_released"=>{:b=>"false"}}], :count=>1, :scanned_count=>1}
dyn.query :table_name => "my_books",
:consistent_read => true,
:select => "SPECIFIC_ATTRIBUTES",
:attributes_to_get => ["item_title"],
:key_conditions => {
"item_id" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "n" => "1" }]
},
"item_title" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "s" => "My Book Title" }]
}
}
# => {:member=>[{"item_title"=>{:s=>"My Book Title"}}], :count=>1, :scanned_count=>1}
dyn.delete_item(
:table_name => "my_books",
:key => {
"item_id" => { "n" => "1" }
}
)
# => {:member=>[], :count=>0, :scanned_count=>0}

Ruby collection out of order

I have the following:
BB_AREAS = {
:about => {:link => "quem somos", :slug => "quem-somos"},
:expositors => {:link => "expositores",:slug => "expositores"},
:map => {:link => "planta", :slug => "planta"},
:activities => {:link => "atividades",:slug => "atividades"},
:address => {:link => "como chegar",:slug => "como-chegar"},
:support => {:link => "apoio", :slug => "apoio"},
:optin => {:link => "cadastro",:slug => "cadastro"},
:how_expositors => {:link => "como expor",:slug => "como-expor"},
:press => {:link => "imprensa",:slug => "imprensa"},
:contact => {:link => "contato",:slug => "contato"},
}
BB_MENU_AREAS = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:link]}
BB_MENU_AREAS_SLUG = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:slug]}
And in the view I have the following:
=render :partial => '/shared/menu', :collection => BB_MENU_AREAS.map {|link, menu| {:link => link, :menu => menu}}, :spacer_template => '/shared/menu_separator'
I want the menu to render in the same order of BB_AREAS, but it is rendered in an arbitrary order.
Hashes are ordered by insertion order in Ruby 1.9+, otherwise they have an internal order.
IMO this data belongs in an array of actual objects, though; roughly:
class Area
attr_accessor :name, :link, :slug
def initialize(name, link, slug)
#name = name
#link = link
#slug = slig
end
end
BB_AREAS = [
Area.new("About", "quem somos", "quem-somos"),
Area.new("Expositors", "expositores", "expositores"),
# etc.
]
If you actually need to extract individual components in order you may.
Ruby Hashs are orderless in Ruby 1.8 and lower. However, in Ruby 1.9 and higher, Hashes are ordered. There is a backward compatible solution though:
BB_AREAS = [
[:about , {:link => "quem somos", :slug => "quem-somos"}],
[:expositors , {:link => "expositores",:slug => "expositores"}],
[:map , {:link => "planta", :slug => "planta"}],
[:activities , {:link => "atividades",:slug => "atividades"}],
[:address , {:link => "como chegar",:slug => "como-chegar"}],
[:support , {:link => "apoio", :slug => "apoio"}],
[:optin , {:link => "cadastro",:slug => "cadastro"}],
[:how_expositors , {:link => "como expor",:slug => "como-expor"}],
[:press , {:link => "imprensa",:slug => "imprensa"}],
[:contact , {:link => "contato",:slug => "contato"}],
]
BB_MENU_AREAS = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:link]}
BB_MENU_AREAS_SLUG = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:slug]}
Nothing do change in your view. Additionally, in this data structure, instead of [key], you need to use .assoc(key)[1].

Getting key value in nested Hash by key array values

Sample hash:
{
"audio" => {
"audio/aac" => ["aac"],
"audio/mpeg" => ["mp3", "mp2"],
"audio/mp4" => ["m4a", "m4b", "m4r", "3gp"],
"audio/ogg" => ["ogg", "oga"],
"audio/flac" => ["flac"],
"audio/speex" => ["spx"],
"audio/x-ms-wma" => ["wma"],
"audio/x-pn-realaudio" => ["rm", "ram"],
"audio/vnd.wave" => ["wav"],
"audio/x-musepack" => ["mpc", "mp+", "mpp"],
"audio/x-aiff" => ["aiff", "aif", "aifc"],
"audio/x-tta" => ["tta"]
},
"video" => {
"video/mp4" => ["mp4"],
"video/mpeg" => ["mpg", "mpeg"],
"video/x-m4v" => ["m4v"],
"video/quicktime" => ["mov"],
"video/x-msvideo" => ["avi"],
"video/x-flv" => ["flv"],
"video/webm" => ["webm"]
}
}
What's the best way given a file extension to get the associated content type (first match is okay)?
Searching for "flac" should return "audio/flac".
Currently I'm using this:
hsh.each_key do |group|
hsh[group].each do |k,v|
return k if v.include?(extension)
end
end
Unraveling that sort of structure is best done when it's created. But, you can loop through the various levels and get something useful from it. If I assign your initial hash to mime_hash I can unravel it using:
Hash[*mime_hash.map{ |av, types| types.map{ |mime_type, extensions| extensions.product([mime_type]) } }.flatten]
or more verbosely:
Hash[
*mime_hash.map{ |av, types|
types.map{ |mime_type, extensions|
extensions.product([mime_type])
}
}.flatten
]
Which will return:
{
"aac" => "audio/aac",
"mp3" => "audio/mpeg",
"mp2" => "audio/mpeg",
"m4a" => "audio/mp4",
"m4b" => "audio/mp4",
"m4r" => "audio/mp4",
"3gp" => "audio/mp4",
"ogg" => "audio/ogg",
"oga" => "audio/ogg",
"flac" => "audio/flac",
"spx" => "audio/speex",
"wma" => "audio/x-ms-wma",
"rm" => "audio/x-pn-realaudio",
"ram" => "audio/x-pn-realaudio",
"wav" => "audio/vnd.wave",
"mpc" => "audio/x-musepack",
"mp+" => "audio/x-musepack",
"mpp" => "audio/x-musepack",
"aiff" => "audio/x-aiff",
"aif" => "audio/x-aiff",
"aifc" => "audio/x-aiff",
"tta" => "audio/x-tta",
"mp4" => "video/mp4",
"mpg" => "video/mpeg",
"mpeg" => "video/mpeg",
"m4v" => "video/x-m4v",
"mov" => "video/quicktime",
"avi" => "video/x-msvideo",
"flv" => "video/x-flv",
"webm" => "video/webm"
}
As you've already realized the data structure you have is horrible to search in the fashion you want. Assuming you're going to be searching the same data over and over what you should do is create an index for it.
There are many ways of doing this but the simplest is probably just to flatten the hash and invert it so that your keys become values and vice-versa. That way you can simply search it by calling content_types['flac']
A section of the example hash might end up like this:
{
"aac" => "audio/aac",
"mp3" => "audio/mpeg",
"mp2" => "audio/mpeg",
"m4a" => "audio/mp4",
"m4b" => "audio/mp4",
"m4r" => "audio/mp4",
"3gp" => "audio/mp4",
"flac" => "audio/flac"
}
Try using rassoc()
Definition:
Searches through the hash comparing obj with the value using ==. Returns the first key-value pair (two-element array) that matches. See also Array#rassoc.
a = {1=> "one", 2 => "two", 3 => "three", "ii" => "two"}
a.rassoc("two") #=> [2, "two"]
a.rassoc("four") #=> nil

Resources