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
Related
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
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": "..."
},
I want to get a specific output from the Typeform API.
This is the response I get back.
Example response:
"answers": [
{
"field": {
"id": "hVONkQcnSNRj",
"type": "dropdown",
"ref": "my_custom_dropdown_reference"
},
"type": "text",
"text": "Job opportunities"
},
{
"field": {
"id": "RUqkXSeXBXSd",
"type": "yes_no",
"ref": "my_custom_yes_no_reference"
},
"type": "boolean",
"boolean": false
}
]
Why does .first work and why does .second not work ?
My OrdersController.rb
items = response.parsed_response["items"]
items.each do |item|
#order = current_user.orders.find_or_create_by(landing_id: item["landing_id"]) do |order|
item["answers"].each do |answer|
order.landing_id = item["landing_id"]
order.email = item["hidden"]["email"]
order.price = item["hidden"]["price"]
order.moduls = item["hidden"]["moduls"]
order.project = item["hidden"]["project"]
order.website = answer.first # This works
order.payment = answer.second # undefined method `second' for #<Hash:0x11f83e78>
end
end
end
You can do
answers.each { |answer| answer[:field] }
or, if you want ids for example
answers.map { |answer| answer.dig(:field, :id) }
Because ruby hash doesn't have any second or last methods. You can access value with the help of keys. e.g. answer[:type], answer[:text]
item["answers"].each do |answer| was an overkill. The solution was as simple as that:
order.website = item["answers"][1]["text] # Access the first field of answers array
order.payment = item["answers"][2]["text] # Access the second field of answers array
I am building a Rails 5 app.
In this app I have connected to the Google Calendar API.
The connection works fine and I get a list of calendars back.
What I need to do is to get the Id and Summary of this JSON object that I get back from Google.
This is what I get
[{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
},
{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
}]
This is what I want to end up with
[{
"id": "xxx.com_asae#group.calendar.google.com",
"title": "My office calendar",
}]
The purpose of this is that I want to populate a selectbox using Selectize plugin
Another way to achieve removing of certain keys in your hash is by using Hash#reject method:
response = { your_json_response }
expected = [response[0].reject {|k| k != :id && k != :summary}]
The original response remains unchanged while a mutated copy of the original response is returned.
You can filter the desierd keys with the select method:
responde = {your_json_response}
expected = [response[0].select{|k,v| ['id','title'].include?(k)}]
response[0] retrieves the hash, and the select compares each key with the ones you want and returns a hash with only those key: value pairs.
EDIT: I missed that you don't have a "title" key on the original response, I would do this then:
response = {your_json_response}
h = response[0]
expected = [{'id' => h['id'], 'title' => h['summary']}]
EDIT 2: Sorry, the first example was not clear that there would be multiple hashes
expected = response.map{|h| {'id' => h['id'], 'title' => h['summary']}}
map iterates over each element of response and returns the result of the block applied for each iteration as an array, so the blocks is apllied to each h and it generates a new hash from it
I suggest this approach.
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }
It returns just the required pairs:
# => [{:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}, {:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}]
To remove duplicates, just do expected.uniq
If you need to change the key name :summary to :title do:
expected = expected.each { |h| h[:title] = h.delete(:summary) }
One liner
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }.each { |h| h[:title] = h.delete(:summary) }.uniq
Of course, maybe it is better to move .uniq as first method expected = response.uniq.each { .....
I'm currently working on a simple hash loop, to manipulate some json data. Here's my Json data:
{
"polls": [
{ "id": 1, "question": "Pensez-vous utiliser le service de cordonnerie/pressing au moins 2 fois par mois ?" },
{ "id": 2, "question": "Avez-vous passé une bonne semaine ?" },
{ "id": 3, "question": "Le saviez-vous ? Il existe une journée d'accompagnement familial." }
],
"answers": [
{ "id": 1, "poll_id": 1, "value": true },
{ "id": 2, "poll_id": 3, "value": false },
{ "id": 3, "poll_id": 2, "value": 3 }
]
}
I want to have the poll_id value and the value from the answers hash. So here's what I code :
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
datas["answers"].each do |answer|
result["polls"] = {"id" => answer["poll_id"], "value" => answer["value"]}
end
polls_json = result.to_json
However, it returns me :
{
"polls": {
"id": 2,
"value": 3
}
}
Here's the output i am looking for :
{
"polls": [
{
"id": 1,
"value": true
},
{
"id": 2,
"value": 3
},
{
"id": 3,
"value": false
}
]
}
It seems that the value is not saved into my loop. I've tried different method but I still cannot find a solution .. Any suggestions?
You should be using reduce here, i.e.
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash[:polls] << { id: data["poll_id"], value: data["value"] }
hash
end
This method iterates through the answers, making available the object supplied to reduce (in this case a hash with a :polls array) to which we pass each data hash.
I'd personally, um, reduce this a little further with the following, although it's at some cost to readability:
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash.tap { |h| h[:polls] << { id: data["poll_id"], value: data["value"] } }
end
It's the cleanest method to achieve what you're looking for, using a built-for-purpose method.
Docs for reduce here: https://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-reduce
(I'd also be inclined to update the variable names - data is already plural, so 'datas' is a little confusing to anyone else coming to your code.)
Edit: #max makes a great point re symbol / string keys from your data - keep that in mind if you attempt to apply this.
try the below:
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
poll_json = []
datas["answers"].each do |answer|
poll_json << {"id" => answer["poll_id"], "value" => answer["value"]}
end
p "json = "#{poll_json}"
{
polls: datas["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
In general use .map to iterate through arrays and hashes and return new objects. .each should only be used when you are only concerned about the side effects (like in a view when you are outputting values).
require 'json'
json = JSON.parse(File.read('data.json'))
result = {
polls: json["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
puts result.to_json
The output is:
{"polls":[{"id":1,"value":true},{"id":3,"value":false},{"id":2,"value":3}]}