Ruby : Most efficient way to rename key and value in hash - ruby-on-rails

I have a array of hashes as follows:
"data":[
{
"Id":"1",
"Name":"John"
},
{
"Id":"2",
"Name":"Robert"
},
{
"Id":"3",
"Name":"Steve"
},
{
"Name":"Tom",
"Country":"USA"
}
]
I want to :
Rename all key Name as First_Name.
Then any First_Name value that is Tom to Thomas.
Something like :
"data":[
{
"Id":"1",
"First_Name":"John"
},
{
"Id":"2",
"First_Name":"Robert"
},
{
"Id":"3",
"First_Name":"Steve"
},
{
"First_Name":"Thomas",
"Country":"USA"
}
]
I have gathered something like
data.map{|h| {"First_Name"=>h['Name']} }
data.map{|h| h['First_Name'] = "Thomas" if h['First_Name'] == "Tom" }
What is the most efficient way of doing this ?

If you are using Ruby 3.0+, you could do something like:
data.each do |hash|
hash.transform_keys!({Name: 'First_Name'})
hash.transform_values! { |v| v == 'Tom' ? 'Thomas' : v }
end
If you are using Ruby versions below 3.0, then you could:
data.each do |hash|
hash.transform_keys! { |k| k == 'Name' ? 'First_Name' : k }
hash.transform_values! { |v| v == 'Tom' ? 'Thomas' : v }
end

I think a better way to edit the data in place is:
data.each {|h| h["First Name"] = h.delete "Name"}
data.each {|h| h["First Name"] = "Tom" if h["First Name"] == "Thomas"}
When you call hash.delete it returns the value of the key/value pair it is deleting. So you can grab that in a new key/value pair with the correct key using the = assignment.
For efficiency just combine it into one loop:
data.each do |h|
h["First Name"] = h.delete "Name"
h["First Name"] = "Tom" if h["First Name"] == "Thomas"
end

Related

check false.present? in Ruby On Rails

I have an API. I want to check all the blank values in the request body and reject them. Also I have few attributes which can have either true / false (boolean) as value.
sample request body
{
"data": {
"id": 123,
"type": "abc",
"attributes": {
"is_abc": false,
"name": "gtd",
"is_dgc": true
}
}
}
I am validating if the request body has any blank values and raise exception if any.
def validate_blank_values(params)
blank_keys = params.map { |key, value| key if value.blank? }.compact
raise exception
end
value.blank? rejects even the boolean attributes when the request has false as its value which is a valid value here. Tried
value.present?
Also behaves the same way. The problem with .nil? is it returns false for empty string also.
''.nil? => false
The problem with .empty? is
false.empty? => (undefined method `empty?' for false:FalseClass)
I want to reject the values when they are
blank
empty string
nil
do not reject when
proper value
false as value
Suggest me a solution for this
There more then one way:
Solution 1
Converting to string, striping spaces and then testing if empty would do the job
value = false
As stated by #engineersmnky With rails you can use blank?:
value.to_s.blank?
With 2 conditions
value.class == String && value.blank?
With only ruby:
value.to_s.strip.empty?
With Regex
/^\s*$/.match?(value.to_s)
With 2 conditions
value.class == String && value.strip.empty?
To recursively test json
Option 1 With map and reduce
def is_empty(tmp)
tmp.map {
|k, v| v.class == Hash ? is_empty(v) : v.to_s.strip.empty?
}.reduce { |r1, r2| r1 || r2 }
end
json = {
"data": {
"id": 123,
"type": "abc",
"attributes": {
"is_abc": false,
"name": "gtd",
"is_dgc": true
}
}
}
puts is_empty(json)
Option 2 With reduce
def is_empty(tmp)
tmp.reduce(false) {
|result, (k, v)| v.class == Hash ? is_empty(v) : result || v.to_s.strip.empty?
}
end
json = {
"data": {
"id": 123,
"type": "abc",
"attributes": {
"is_abc": false,
"name": "gtd",
"is_dgc": true
}
}
}
puts is_empty(json)
You need to use multiple conditions here otherwise it won't work.
Try below condition
value.present? || value.class == FalseClass || value.class == TrueClass
P.S. - blank_keys = params.map { |key, value| key if value.blank? }.compact is not checking for nested values of attributes

Rails API Globalize get all translations

In an API-only rails app using globalize - how do I return all the translations for a model?
ie.
[
{
"id": 1,
"name_ar": "كرستوفر نولان",
"name_en": "Christopher Nolan",
"name_fr": "Christopher Nolan"
},
{
"id": 2,
"name_ar": "ميشيل جوندري",
"name_en": "Michael Gondry",
"name_fr": "Michael Gondry"
},
// ...
]
I've been searching for quite some time about this but I have failed to find a solution.
You can do something like this: (not a complete efficient solution but just a try if that helps)
# translated attribute names
attrs = %w[title description]
def translated_attributes(objects, attributes)
result = []
objects.each do |obj|
trans = {}
obj.translations.each do |tr|
trans['id'] = obj.id
attributes.each do |attr|
trans[attr + '_' + tr['locale']] = tr[attr]
end
end
result << trans
end
result
end
translated_attributes(objects, attrs)
Please change the names according to your application and pass the attributes accordingly.
You can do something like this:
result = {}
Director.find_each do |director|
result[:id] = director.id
director.translations.each { |t| result["name_#{t[:locale]}"], result["description_#{t[:locale]}"] = t.title, t.description }
end
to get
{
"id": 1,
"name_ar": "كرستوفر نولان",
"name_en": "Christopher Nolan",
"name_fr": "Christopher Nolan",
"description_ar": "...",
"description_en": "...",
"description_fr": "..."
},

Update one array of hash based on key value of other array of hash

I have following two array of hashes. I am trying to remove the record from doctor array hash whose doctor_patient_id doesnt not exist in doctor_patient_id array of patient_and_doctor array of hash.
doctor = [
{ :doctor_patient_id=>"abc",
:doctor_id=>"d1"
},
{ :doctor_patient_id=>"def",
:doctor_id=>"d2"
},
{ :doctor_patient_id=>"ghi",
:doctor_id=>"d3"
}
]
patient_and_doctor = [
{ :patient_id=>"11e8f37477ab7028a66b210b9699def9",
:doctor_patient_id=>[ "def", "zkj", "cps" ]
},
{ :patient_id=>"11e8f37481fabfe68630f5da2e22dceb",
:doctor_patient_id=>[ "uio", "ghi", "jkk" ]
}
]
expected output is:
doctor = [
{ :doctor_patient_id=>"def",
:doctor_id=>”d2”
},
{ :doctor_patient_id=>"ghi",
:doctor_id=>”d3”
}
]
I tried to do something like below but no luck,
patient_and_doctor.each do |dp|
data = doctor.map {|d| d[:doctor_patient_id].include?
dp[:doctor_patient_id] }
end
How can i achieve this?
valid_ids = patient_and_doctor.flat_map { |h| h[:doctor_patient_id] }
# => ["def", "zkj", "cps", "uio", "ghi", "jkk"]
doctor.select { |h| valid_ids.include? h[:doctor_patient_id] }
# => [{:doctor_patient_id=>"def", :doctor_id=>"d2"},
# {:doctor_patient_id=>"ghi", :doctor_id=>"d3"}]
use select! instead of select if you wish to mutate your doctor array instead of returning a new one.
Following can get required answer,
doctor.select { |x| patient_and_doctor.map { |x| x[:doctor_patient_id] }.flatten.include?(x[:doctor_patient_id]) }

Ruby refactoring: converting array to hash

Here's what I get in Rails params:
obj => {
"raw_data" =>
[
{ "id" => "1", "name" => "John Doe" },
{ "id" => "2", "name" => "Jane Doe" }
]
}
I have to transform into a following object:
obj => {
"data" =>
{
"1" => { "name" => "John Doe" },
"2" => { "name" => "Jane Doe" }
}
}
Here's the code I have working so far:
if obj[:raw_data]
obj[:data] = Hash.new
obj[:raw_data].each do |raw|
obj[:data][raw[:id]] = Hash.new
obj[:data][raw[:id]][:name] = raw[:name] if raw[:name].present?
end
end
obj.delete(:raw_data)
Is there a way to refactor it? Maybe using map. Note that data structure has to change from array to hash as well.
Thanks for any tips.
Here's one way:
obj = {
"raw_data" => [
{ "id" => "1", "name" => "John Doe" },
{ "id" => "2", "name" => "Jane Doe" }
]
}
data = obj["raw_data"].map do |item|
item = item.dup
[ item.delete('id'), item ]
end
obj2 = { "data" => data.to_h }
# => { "data" =>
# { "1" => { "name" => "John Doe" },
# "2" => { "name" => "Jane Doe" }
# }
# }
If you're using Rails you can use the Hash#except method from ActiveSupport to make it a little more succinct:
data = obj["raw_data"].map {|item| [ item["id"], item.except("id") ] }
obj2 = { "data" => data.to_h }
d = obj[:raw_data]
keys = d.map { |h| h["id"] }
values = d.map { |h| h.except("id") }
Hash[ keys.zip(values) ]
# or as a oneliner
Hash[ d.map { |h| h["id"] }.zip(d.map { |h| h.except("id")) ]
# => {"1"=>{"name"=>"John Doe"}, "2"=>{"name"=>"Jane Doe"}}
This special Hash[] syntax lets you create a hash from a array of keys and an array of values.
Hash.except(*args) is an ActiveSupport addition to the hash class which returns a new key without the keys in the blacklist.
In rails, you can use index_by method:
obj = {raw_data: [{id: "1", name: "John Doe"}, {id: "2", name: "Jane Doe"}]}
obj2 = {
data: obj[:raw_data].index_by {|h| h[:id]}.each {|_,h| h.delete(:id)}
} #=> {:data=>{"1"=>{:name=>"John Doe"}, "2"=>{:name=>"Jane Doe"}}}
One downfall of this is that it will modify the original data by deleting id property. If this is unacceptable, here is modified, safe version:
obj2 = {
data: obj[:raw_data].map(&:clone).index_by {|h| h[:id]}.each {|_,h| h.delete(:id)}
} #=> {:data=>{"1"=>{:name=>"John Doe"}, "2"=>{:name=>"Jane Doe"}}}
I assume you mean obj = {...} and not obj => {...}, as the latter is not a valid object. If so:
{ "data" => obj["raw_data"].each_with_object({}) { |g,h|
h[g["id"]] = g.reject { |k,_| k == "id" } } }
#=> {"data"=>{"1"=>{"name"=>"John Doe"}, "2"=>{"name"=>"Jane Doe"}}}
If obj can be mutated, you can simplify a bit:
{ "data" => obj["raw_data"].each_with_object({}) { |g,h| h[g.delete("id")]=g } }
As an improved non-mutating solution, #Max suggested a Rails' tweak:
{ "data" => obj["raw_data"].each_with_object({}) { |g,h| h[g["id"]] = g.except("id") } }
That looks good to me, but as I don't know rails, I'm taking that advice at face value.

Ruby use find to loop through an array and return the matching value

I am trying to loop through an array using find to find and return a specific id.
This is my structure:
{
"employees": [
{
"emp_id": "1",
"tutorials": [
{
"id": "test1"
},
{
"id": "test2"
},
{
"id": "test3"
},
{
"id": "test4"
},
{
"id": "test5"
}
]
}
]
}
So basically I am trying to see if the above structure contains a tutorial id of 'test3' and return it.(i.e return 'test3' in this case)
I can get the desired result using a combination of map and find like this:
my_tutorial = employees.map { |employee|
employee.tutorials.find { |tutorial|
tutorial.id == 'test3'
}
}.first
my_tutorial
But I want to know if there is a better way using find . I tried the following but it returns the ruby object instead of the id.
employees.find { |employee|
employee.tutorials.find { |tutorial|
tutorial.id == 'test3'
}
}
Here is what i did to make it work using find. Not sure if it is any better:
my_id = employees.find { |employee|
employee.tutorials.find { |tutorial|
tutorial.id == 'test3'
}
}
my_id.tutorials.first.id
If you have to get only the first record do as follows:
employees[0].tutorials.detect {|r| r.id == 'test3' }

Resources