I'm working with an API that returns a hash to represent a product:
prod = API.getProduct(id)
prod["name"] => "Widget"
The problem arrises because not all products contain identical attribute pages, so I find myself doing a lot of one-off error catching- some products will have a key for size or color, some won't.
What's the easiest way to get to prod["non-existent attribute"] => "NA"?
As Dave Newton said, you can add the default value to the hash constructor:
hash = Hash.new { |hash, key| hash[key] = "NA" }
hash[:anything] == "NA" # => true
Or use the #default method:
hash = Hash.new
hash.default = "NA"
hash[:anything] == "NA" # => true
EDIT The quick syntax for setting the default value when initializing the hash is:
hash = Hash.new("NA")
hash[:anything] == "NA" # => true
Take a look at this: http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-default
You can use prod.default = "NA".
Related
Let's say we're using .dig in Ruby like this:
some_hash = {}
some_hash.dig('a', 'b', 'c')
# => nil
which returns nil
Is there a method where I can assign a value to the key c if any of the other ones are present? For example if I wanted to set c I would have to write:
some_hash['a'] = {} unless some_hash['a'].present?
some_hash['a']['b'] = {} unless some_hash['a']['b'].present?
some_hash['a']['b']['c'] = 'some value'
Is there a better way of writing the above?
That can be easily achieved when you initialize the hash with a default like this:
hash = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
hash[:a][:b][:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}
Setting nested values in that hash with nested defaults can partly be done with dig (apart from the last key):
hash.dig(:a, :b)[:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}
The following returns the default "client?":
class ClientMap
def initialize
##clients = {"DP000459": "BP"}
##clients.default = "client?"
end
def get(id)
return ##clients[:id]
end
end
clientMap = ClientMap.new
cKey = "DP000459"
puts clientMap.get(cKey)
Could anybody explain why I cannot retrieve anything but the 'default'?
You've got two problems. First, you are using the symbol syntax in your hash, which works only if your keys are symbols. If you want keys to be strings, you need to use hash-rocket syntax: ##clients = {'DP000459' => 'BP'}.
Second, your method returns clients[:id] regardless of what parameter is provided. The key is the symbol :id rather than the local variable id. You need to change this to ##clients[id].
Here's a cleaned-up version of what you want:
class ClientMap
def initialize
##clients = {'DP000459' => 'BP'}
##clients.default = 'client?'
end
def get(id)
##clients[id]
end
end
I've also taken the liberty of making the spacing more Ruby-idiomatic.
Finally, for variable names in Ruby, use snake_case:
>> client_map = ClientMap.new
>> c_key = 'DP000459'
>> client_map.get(c_key)
#> "BP"
Look at these code:
h = { foo: 'bar' } # => {:foo=>"bar"}
h.default = 'some default value' # => "some default value"
h[:foo] # => "bar"
h[:non_existing_key] # => "some default value"
You can read here about Hash#default method
Returns the default value, the value that would be returned by hsh if
key did not exist in hsh
I have the following snippet of code to fetch certain columns from the db:
#data = Topic.select("id,name").where("id in (?)",#question.question_topic.split(",")).map(&:attributes)
In the resulting Array of Hashes which is :
Current:
#data = [ { "id" => 2, "name" => "Sports" }]
To be changed to:
#data = [ { "id" => "2", "name" => "Sports" }]
I want to convert "id" to string from fixnum. Id is integer in the db. What is the cleanest way to do this?
Note: After using .map(&:attributes) it is not an active record relation.
You can do it with proper map usage:
topics = Topic.select("id,name").where("id in (?)",#question.question_topic.split(","))
#data = topics.map do |topic|
{
'id' => topic.id.to_s,
'name' => topic.name
}
end
What you're looking for is simply
#data.each { |obj| obj["id"] = obj["id"].to_s }
There isn't really a simpler way (I think that's straightforward enough anyway).
Going by the title which implies a different question - converting every value in the hash to a string you can do this:
#data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
Just leaving that there anyway.
You can use Ruby's #inject here:
#data.map do |datum|
new_datum = datum.inject({}) do |converted_datum, (key, value)|
converted_datum[key] = value.to_s
converted_datum
end
end
This will work to convert all values to strings, regardless of the key.
If you are using Rails it can be even cleaner with Rails' #each_with_object:
#data.map do |datum|
datum.each_with_object({}) do |(key, value), converted_datum|
converted_datum[key] = value.to_s
end
end
This will iterate all the key names in the hash and replace the value with the #to_s version of the datum associated with the key. nil's converted to empty strings. Also, this assumes you don't have complex data within the hash like embedded arrays or other hashes.
def hash_values_to_string(hash)
hash.keys.each {|k| hash[k]=hash[k].to_s}; hash
end
I'm trying to retrieve multiple values from a database into a single variable and return the whole thing. Here is what I am doing
my_hash = {
'name' => 'John'
'current_location' => 'Sweden'
}
Now I need to go into database and retrieve all records and store them into a single variable, and then i need to add that variable into my_hash so I can return the whole thing. How would I do that?
Example:
last_names = Names.where('first_name = ?', 'John').select('last_name').last_name
my_hash.add(last_names)
return my_hash
Now that above does not works, can somebody tell me proper way to achieve this?
are you trying to do the following?
my_hash = {
'name' => 'John'
'current_location' => 'Sweden'
}
my_hash['last_names'] = Names.where('first_name = ?', 'John')
.select('last_name')
.map { |name| name.last_name }
# or shorthand version .map(&:last_name)
return my_hash
Updated
# get name objects from the database
# add select as an optimization if desired
last_name_list = Names.where('first_name = ?', 'John')
# get an array of only the last_name fields
last_names = last_name_list.map { |name| name.last_name }
# assign the array to the new hash key 'last_names'
my_hash['last_names'] = last_names
see http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-map for documentation on map, note that map and collect are the same
another example
names = Names.where('updated_at >= ?', Date.parse('2013-01-01'))
# get an array of 'full names'
full_names = names.map do |name|
"#{name.first_name} #{name.last_name}"
end
I have an array that contains dates and values. An example of how it might look:
[
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
Notice that some of the dates repeat. I'm trying to output this in a table format and I only want to show unique dates. So I loop through it with the following code to get my desired output.
prev_date = nil
#reading_schedule.reading_plans.each do |plan|
use_date = nil
if plan.assigned_date != prev_date
use_date = plan.assigned_date
end
prev_date = plan.assigned_date
plan.assigned_date = use_date
end
The resulting table will then look something like this
1/1/2010 aa
bb
1/2/2010 cc
dd
1/3/2010 ee
This work fine but I am new to ruby and was wondering if there was a better way to do this.
Enumerable.group_by is a good starting point:
require 'pp'
asdf = [
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
pp asdf.group_by { |n| n.keys.first }.map{ |a,b| { a => b.map { |c| c.to_a.last.last } } }
# >> [{"1/1/2010"=>["aa", "bb"]}, {"1/2/2010"=>["cc", "dd"]}, {"1/3/2010"=>["ee"]}]
Which should be a data structure you can bend to your will.
I don't know as though it's better, but you could group the values by date using (e.g.) Enumerable#reduce (requires Ruby >= 1.8.7; before that, you have Enumerable#inject).
arr.reduce({}) { |memo, obj|
obj.each_pair { |key, value|
memo[key] = [] if ! memo.has_key?(key);
memo[key] << value
}
memo
}.sort
=> [["1/1/2010", ["aa", "bb"]], ["1/2/2010", ["cc", "dd"]], ["1/3/2010", ["ee"]]]
You could also use Array#each to similar effect.
This is totally a job for a hash.
Create a hash and use the date as the hashkey and an empty array as the hashvalue.
Then accumulate the values from the original array in the hashvalue array