Rails 5: How do I loop over a hash with each do - ruby-on-rails

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

Related

How to take keep parts of an array and form a new 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 { .....

Iterate through a hash. However, my value is changing every time

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}]}

how to read individual data from the content of remote JSON file in Ruby on Rails?

I am trying to read individual data from the content of json API on Oil and Gas Authority website; however the code I have returns all the data. Any help would be highly appreciated.
require 'json'
require 'open-uri'
def index
url='http://data-ogauthority.opendata.arcgis.com/datasets/ab4f6b9519794522aa6ffa6c31617bf8_0.geojson'
#result = JSON.parse open(url).read
end
This my index view:
<% #result.each do |row| %>
<%= row %>
<% end %>
Given that the API (as you are currently using it) returns a JSON structure like this:
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"properties":{
"FIELDNAME":"GRYPHON",
"FIELDTYPE":"OIL",
"NAME_SHORT":"GRYPHON",
"STATUS":"PRODUCING",
"DISC_DATE":"1987/07",
"DISC_WELL":"9/18b-7",
"STAT_CODE":"800",
"PROD_DATE":"1993/10",
"DEPTH_M":"111.86",
"DET_STATUS":"DETERMINED",
"ORIG_OP":"KERR-MCGEE",
"ORIG_LIC":"P.496",
"ORIG_LIC_2":"P.478",
"ORIG_LIC_3":"P.257",
"ORIG_LIC_4":"P.103",
"ORIG_LIC_5":" ",
"CURR_OPER":"MAERSK OIL NORTH SEA UK LIMITED",
"FIELDDATA":"https://itportal.decc.gov.uk/fields/fields_index/ukcs+field+information+listed+by+field+name/183.htm",
"OBJECTID":16,
"OGA_COP":null
},
"geometry":{
"type":"Polygon",
"coordinates":[
[
[1.5701447246411744,59.35253688325039],
...
]
]
}
},
...
]
}
You could do something like:
<% #result[:features].each do |feature| %>
<%= feature[:properties][:FIELDNAME] %>
<%= feature[:properties][:FIELDTYPE] %>
...
<% end %>
Your JSON file looks to be something like 1.3MB. So, unless you can figure out how to filter your results on the API side (using query params, I would suppose), you may end up with various performance issues in retrieving the JSON.
And, you may want to do:
#result = JSON.parse(open(url).read).with_indifferent_access
So that you can use symbols to access hash elements as well as strings.
One thing to add to #jvillian answer is that if one of the keys is nil then calling this branch's subsequent keys will result in undefined method '[]'. Ruby 2.3+ has a new method called dig which will simply return nil. More on dig in my answer to this question.
Also to add to #jvillian answer, you can fetch the filtered information using this link; for example, taking into account the fields you need:
FIELDNAME
FIELDTYPE
STATUS
DISC_DATE
DISC_WELL
You could create query that will result in the following response:
{
"displayFieldName": "FIELDNAME",
"fieldAliases": {
"FIELDNAME": "Field Name",
"FIELDTYPE": "Field Type",
"STATUS": "Status",
"DISC_DATE": "Discovery Date",
"DISC_WELL": "Discovery Well"
},
"fields": [
{
"name": "FIELDNAME",
"type": "esriFieldTypeString",
"alias": "Field Name",
"length": 32
},
{
"name": "FIELDTYPE",
"type": "esriFieldTypeString",
"alias": "Field Type",
"length": 4
},
{
"name": "STATUS",
"type": "esriFieldTypeString",
"alias": "Status",
"length": 50
},
{
"name": "DISC_DATE",
"type": "esriFieldTypeString",
"alias": "Discovery Date",
"length": 20
},
{
"name": "DISC_WELL",
"type": "esriFieldTypeString",
"alias": "Discovery Well",
"length": 20
}
],
"features": [
{
"attributes": {
"FIELDNAME": "GRYPHON",
"FIELDTYPE": "OIL",
"STATUS": "PRODUCING",
"DISC_DATE": "1987/07",
"DISC_WELL": "9/18b-7"
}
},
{
"attributes": {
"FIELDNAME": "BRAEMAR",
"FIELDTYPE": "COND",
"STATUS": "PRODUCING",
"DISC_DATE": "1995/05",
"DISC_WELL": "16/03b-8Z"
}
},
...
]
}
This file will be now 76KB, and you can extract the data in almost the same way, just change properties for attributes, i.e.:
<% #result[:features].each do |feature| %>
<%= feature[:attributes][:FIELDNAME] %>
<%= feature[:attributes][:FIELDTYPE] %>
...
<% end %>

Rails merge multiple json response

Can anyone help me with this problem?
So, here is the problem, I want to merge this query response:
#energy = Alert.where(["alert_type = ?", "Energy"]).last.as_json
#cost = Alert.where(["alert_type = ?", "Cost"]).last.as_json
Then I merge those object with:
#current_notif = #energy.merge(#cost)
But those just give me #cost object like this:
{
"alert_type": "Cost",
"value": 30000000,
"status": "Cost exceeds limit",
"created_at": "2017-06-03T15:31:21.156+07:00",
"updated_at": "2017-06-03T15:31:21.156+07:00",
"home_id": 2
}
Rather than a merged #energy + #cost like this:
{ {"alert_type": "Energy",
"value": 384455.813978742,
"status": "Energy too high",
"created_at": "2017-05-31T11:31:12.907+07:00",
"updated_at": "2017-05-31T11:31:12.907+07:00",
"home_id": 2 },
{
"alert_type": "Cost",
"value": 30000000,
"status": "Cost exceeds limit",
"created_at": "2017-06-03T15:31:21.156+07:00",
"updated_at": "2017-06-03T15:31:21.156+07:00",
"home_id": 2
}
}
If you want you could "join" both values, and then over that use as_json:
[#energy, #cost].as_json
# [{"alert_type": "Energy", ... }, {"alert_type": "Cost", ... }]
Or if you want you could use the IN expression, in order to deal with ActiveRecord instead having to customize the result this gives you:
Alert.where(alert_type: ['Energy', 'Cost']).as_json
# [{"alert_type": "Energy", ... }, {"alert_type": "Cost", ... }]
This is happening because that's how merge works.
hash = {:name => "Ade", :gender => "Male"}.merge(:name => "Bob")
puts hash # {:name=>"Bob", :gender=>"Male"}
Solution:
results = [ #energy, #cost ]
results.each do |result|
puts result['alert_type'] # Energy, Cost
end

Rails 4 - Iterate through nested JSON params

I'm passing nested JSON into rails like so:
{
"product": {
"vendor": "Acme",
"categories":
{
"id": "3",
"method": "remove",
},
"categories":
{
"id": "4"
}
}
}
in order to update the category on a product. I am trying to iterate through the categories attribute in my products_controller so that I can add/remove the product to multiple categories at once:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: params[:product][:categories][:id])
if params[:product][:categories][:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end
However, this only uses the second 'categories' ID in the update and doesn't iterate through both.
Example response JSON:
{
"product": {
"id": 20,
"title": "Heavy Duty Aluminum Chair",
"product_price": "47.47",
"vendor": "Acme",
"categories": [
{
"id": 4,
"title": "Category 4"
}
]
}
}
As you can see, it only added the category with ID = 4, and skipped over Category 3.
I'm fairly new to rails so I know I'm probably missing something obvious here. I've played around with the format of the JSON I'm passing in as well but it only made things worse.
You need to change your JSON structure. As you currently have it, the second "categories" reference will override the first one since you can only have 1 instance of a key. To get what you want, you should change it to:
{
"product": {
"vendor": "Acme",
"categories": [
{
"id": "3",
"method": "remove",
},
{
"id": "4"
}
]
}
}
You will also need to change your ruby code to look like:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: u[:id])
if u[:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end

Resources