Fetch under one key ruby - ruby

I have this json:
{"user"=>
{"name"=>"Lebron James",
"email"=>"lebron.james#gmial.com",
"time_zone"=>"America/Chicago",
"contact"=>
[{"id"=>"PO0JGV7",
"type"=>"email_contact_method_reference",
"summary"=>"Default",
"self"=>
"https://pagerduty.com/users/000000/contact/000000",
"html_url"=>nil},
{"id"=>"000000",
"type"=>"phone_contact_method_reference",
"summary"=>"Mobile",
"self"=>
"https://pagerduty.com/users/000000/contact/000000",
"html_url"=>nil},
{"id"=>"000000",
"type"=>"push_notification_contact_method_reference",
"summary"=>"XT1096",
"self"=>
"https://api.pagerduty.com/users/000000/contact/000000",
"html_url"=>nil},
{"id"=>"000000",
"type"=>"sms_contact_method_reference",
"summary"=>"Mobile",
"self"=>
"https://pagerduty.com/users/000000/methods/000000",
"html_url"=>nil}],
I want to be able to retrieve the values of the self keys, but only the ones that has "type" => "email_contact_method_reference" and "summary"=>"Mobile". This is what I thought would work.
phone = File.open("employee_phone_api.txt", "w+")
jdoc.fetch("user").fetch("contact_methods").each do |contact|
if contact["type"] == "email_contact_method_reference" and contact["summary"] == "Mobile"
phone.puts contact["self"]
else
end
end
Thoughts? And/or suggestions?

No need to use #each, as there are more expressive ways of handling this problem. As with many Ruby problems, you want to get an Array and then transform it. In this case, you want to select certain contacts and then pull out particular values.
Your sample hash has a "contact" key but not a "contact_methods" key. I'm using "contact" for my example. Also, your sample contains no objects that meet the criteria, so I'm modifying it to include one.
First we get an Array of all the contacts:
contacts = jdoc.fetch("user").fetch("contact")
Then we filter them to the desired type using Enumerable#select, which results in an Array of a single Hash object:
email_contacts = contacts.select { |contact| contact['type'] == 'email_contact_method_reference' && contact['summary'] == 'Mobile' }
#=> [{"id"=>"PO0JGV7", "type"=>"email_contact_method_reference", "summary"=>"Mobile", "self"=>"https://pagerduty.com/users/000000/contact/000000", "html_url"=>nil}]
Next we map out just the information we want:
urls = email_contacts.map { |contact| contact['self'] }
This results in urls being assigned an Array of a single string:
#=> ["https://pagerduty.com/users/000000/contact/000000"]
In the real world, you will want to have a method that accepts arguments, making the logic flexible. You might do something like this:
def fetch_urls(doc, type, summary)
doc.fetch("user").fetch("contact")
.select { |contact| contact['type'] == type && contact['summary'] == summary }
.map { |contact| contact['self'] }
end
>> fetch_urls(jdoc, 'email_contact_method_reference', 'Mobile')
#=> ["https://pagerduty.com/users/000000/contact/000000"]
Now that you have a working method, you can use it in your file writer:
>> phone = File.open("employee_phone_api.txt", "w+")
>> phone.puts fetch_urls(jdoc, 'email_contact_method_reference', 'Mobile').join("\n")
>> phone.close
>> File.read(phone)
#=> "https://pagerduty.com/users/000000/contact/000000\n"

Related

Parsing recursive hashes into data records in Ruby

I've struggled with this problem for a while, and I'm finally going to ask here for help.
Take a very straightforward hash that represents some event:
{
:eventkey=>"someeventkey",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=> {
:phone=>"1-936-774-6886",
:email=>"dasia_schuster#wisokytrantow.com",
:pageUrl=>"http://ortiz.info/joe"
}
}
My goal is to create a 'master record' for the entire hash, with the fields in the record being all the keys that do not contain values that are also hashes. When I run into a value that is a hash (in this case 'details'), I need to create a separate record for each k/v pair in that hash bearing the same record id as the parent master record.
I'm not getting the recursion right somehow. Ideally I would get back a single primary record:
{
:recordid=>"some-generated-record-id",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=>nil
}
And a distinct entry for each key in the nested hash:
{
:recordid=>"some-generated-detail-record-id",
:parentid=>"the-parent-id-from-the-master-record",
:phone=>"1-936-774-6886"
}
{
:recordid=>"another-generated-detail-record-id",
:parentid=>"the-same-parent-id-from-the-master-record",
:email=>"dasia_schuster#wisokytrantow.com"
}
And so on. I'm trying to get this set of records back as an array of hashes.
I've gotten as far as being able to generate the master record, as well as a detail record, but the detail record contains all the keys in the detail.
def eventToBreakout(eventhash,sequenceid = -1, parentrecordid = nil, records = [])
recordid = SecureRandom.uuid
sequenceid += 1
recordstruc = {:record_id => recordid, :parent_record_id => parentrecordid, :record_processed_ts => Time.now, :sequence_id => sequenceid}
eventhash.each_pair do |k,v|
if recurse?(v)
eventToBreakout(v,sequenceid,recordid,records)
else
if !recordstruc.keys.include?(k)
recordstruc[k]=v
end
end
end
records << recordstruc
records
end
I've included my code and here is the output I'm currently getting from it.
[{:record_id=>"ed98be89-4c1f-496e-beb4-ede5f38dd549",
:parent_record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>1,
:phone=>"1-756-608-8114",
:email=>"hipolito_wilderman#morar.co",
:pageUrl=>"http://haag.net/alexie.marvin"},
{:record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:parent_record_id=>nil,
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>0,
:eventts=>2016-04-25 22:10:32 -0500,
:web_id=>"a61c57ae-3a01-4994-8803-8d8292df3338",
:apikey=>"9adbc7a4-03ff-4fcc-ac81-ae8d0ee01ef0"}]
Maybe you want something along these lines?
input = { id: 'parent', value: 'parent value', child: { child_value: 1}}
record = {}
input.each do |k,v|
if v.is_a? Hash
v[:parent_id] = input[:id]
(record[:children] ||= []) << v
else
record[k] = v
end
end
puts record
# {:id=>"parent", :value=>"parent value", :children=>[{:child_value=>1, :parent_id=>"parent"}]}
By the way this is a good example to get started with "spec" or "test" frameworks like minitest or rspec (both can be used for both). You have defined input and expected output already and "just" need to code until all test/spec-runs are green.

Ruby If statement

I am trying to do a post and run some if statement. What I want to do is:
check all fields are filled
if all fields are filled move on to next step, or else reload page
check if already in data base
add if not already in data base
post "/movies/new" do
title = params[:title]
year = params[:year]
gross = params[:gross]
poster = params[:poster]
trailer = params[:trailer]
if title && year && gross && poster && trailer
movie = Movie.find_by(title: title, year: year, gross: gross)
if movie
redirect "/movies/#{movie.id}"
else
movie = Movie.new(title: title, year: year, gross: gross, poster: poster, trailer: trailer)
if movie.save
redirect "/movies/#{movie.id}"
else
erb :'movies/new'
end
end
else
erb :'movies/new'
end
end
I don't think my if statement is correct. It works even if all my fields are not filled
Your code is doing a lot of work in one single method. I would suggest to restructure it into smaller chunks to make it easier to manage. I mostly code for Rails, so apologies if parts of these do not apply to your framework.
post "/movies/new" do
movie = find_movie || create_movie
if movie
redirect "/movies/#{movie.id}"
else
erb :'movies/new'
end
end
def find_movie
# guard condition to ensure that the required parameters are there
required_params = [:title, :year, :gross]
return nil unless params_present?(required_params)
Movie.find_by(params_from_keys(required_params))
end
def create_movie
required_params = [:title, :year, :gross, :poster, :trailer]
return nil unless params_present?(required_params)
movie = Movie.new(params_from_keys(required_params))
movie.save ? movie : nil # only return the movie if it is successfully saved
end
# utility method to check whether all provided params are present
def params_present?(keys)
keys.each {|key| return false if params[key].blank? }
true
end
# utility method to convert params into the hash format required to create / find a record
def params_from_keys(keys)
paras = {}
keys.each { |key| paras.merge!(key: params[key]) }
paras
end
Even if you type nothing in the HTML fields, they will still be submitted as empty strings.
You can avoid having empty parameters by, for example, filtering them:
post '/movies/new' do
params.reject! { |key, value| value.empty? }
# rest of your code
end
Also I would rather post to /movies rather than to /movies/new, that's more REST-wise.
Try if condition to check fields are blank like below -
unless [title, year, gross, poster, trailer].any?(&:blank?)
This will check any of the field should not be nil or blank("").

Difficulty processing json with ruby

I have the following json...
{
"NumPages":"17",
"Page":"1",
"PageSize":"50",
"Total":"808",
"Start":"1",
"End":"50",
"FirstPageUri":"/v3/results?PAGE=1",
"LastPageUri":"/v3/results?PAGE=17",
"PreviousPageUri":"",
"NextPageUri":"/v3/results?PAGE=2",
"User":[
{
"RowNumber":"1",
"UserId":"86938",
"InternalId":"",
"CompletionPercentage":"100",
"DateTimeTaken":"2014-06-18T01:43:25Z",
"DateTimeLastUpdated":"2014-06-18T01:58:11Z",
"DateTimeCompleted":"2014-06-18T01:58:11Z",
"Account":{
"Id":"655",
"Name":"Technical Community College"
},
"FirstName":"Matthew",
"LastName":"Knice",
"EmailAddress":"knice#gmail.com",
"AssessmentResults":[
{
"Title":"Life Factors",
"Code":"LifeFactors",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"58",
"Readiness":"fail",
"DateTimeCompleted":"2014-06-18T01:46:00Z"
},
{
"Title":"Learning Styles",
"Code":"LearnStyles",
"IsComplete":"0"
},
{
"Title":"Personal Attributes",
"Code":"PersonalAttributes",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"52.08",
"Readiness":"fail",
"DateTimeCompleted":"2014-06-18T01:49:00Z"
},
{
"Title":"Technical Competency",
"Code":"TechComp",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"100",
"Readiness":"pass",
"DateTimeCompleted":"2014-06-18T01:51:00Z"
},
{
"Title":"Technical Knowledge",
"Code":"TechKnowledge",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"73.44",
"Readiness":"question",
"DateTimeCompleted":"2014-06-18T01:58:00Z"
},
{
"Title":"Reading Rate & Recall",
"Code":"Reading",
"IsComplete":"0"
},
{
"Title":"Typing Speed & Accuracy",
"Code":"Typing",
"IsComplete":"0"
}
]
},
{
"RowNumber":"2",
"UserId":"8654723",
"InternalId":"",
"CompletionPercentage":"100",
"DateTimeTaken":"2014-06-13T14:37:59Z",
"DateTimeLastUpdated":"2014-06-13T15:00:12Z",
"DateTimeCompleted":"2014-06-13T15:00:12Z",
"Account":{
"Id":"655",
"Name":"Technical Community College"
},
"FirstName":"Virginia",
"LastName":"Bustas",
"EmailAddress":"bigBusta#students.college.edu",
"AssessmentResults":[
{
...
I need to start processing where you see "User:" The stuff at the beginning (numpages, page, ect) I want to ignore. Here is the processing script I am working on...
require 'csv'
require 'json'
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
JSON.parse(File.open("sample.json").read).each do |hash| #open json to parse
csv << hash.values
end
end
Right now this fails with the error:
convert.rb:6:in `block (2 levels) in <main>': undefined method `values' for ["NumPages", "17"]:Array (NoMethodError)
I have ran the json through a parser, and it seems to be valid. What is the best way to only process the "User" data?
You have to look at the structure of the JSON object being created. Here's a very small subset of your document being parsed, which makes it easier to see and understand:
require 'json'
foo = '{"NumPages":17,"User":[{"UserId":12345}]}'
bar = JSON[foo]
# => {"NumPages"=>17, "User"=>[{"UserId"=>12345}]}
bar['User'].first['UserId'] # => 12345
foo contains the JSON for a hash. bar contains the Ruby object created by the JSON parser after it reads foo.
User is the key pointing to an array of hashes. Because it's an array, you have to specify which of the hashes in the array you want to look at, which is what bar['User'].first does.
An alternate way to access that sub-hash is:
bar['User'][0]['UserId'] # => 12345
If there were multiple hashes inside the array, you could access them by using the appropriate index value. For example, if there are two hashes, and I want the second one:
foo = '{"NumPages":17,"User":[{"UserId":12345},{"UserId":12346}]}'
bar = JSON[foo]
# => {"NumPages"=>17, "User"=>[{"UserId"=>12345}, {"UserId"=>12346}]}
bar['User'].first['UserId'] # => 12345
bar['User'][0]['UserId'] # => 12345
bar['User'][1]['UserId'] # => 12346
I'm wondering if I am going down the wrong road with the JSON.parse(File.open("sample.json").read).each do |hash|?
Yes, you are. You need to understand what you're doing, and break your code into digestible pieces so they make sense to you. Consider this:
require 'csv'
require 'json'
json_object = JSON.parse(File.read("sample.json"))
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
csv << %w[RowNumber UserID AccountID AccountName FirstName LastName EmailAddress]
json_object['User'].each do |user_hash|
puts 'RowNumber: %s' % user_hash['RowNumber']
puts 'UserID: %s' % user_hash['UserID']
account = user_hash['UserID']['Account']
puts 'Account->Id: %s' % account['Id']
puts 'Account->Name: %s' % account['Name']
puts 'FirstName: %s' % user_hash['FirstName']
puts 'LastName: %s' % user_hash['LastName']
puts 'EmailAddress: %s' % user_hash['EmailAddress']
csv << [
user_hash['RowNumber'],
user_hash['UserID'],
account['Id'],
account['Name'],
user_hash['FirstName'],
user_hash['LastName'],
user_hash['EmailAddress']
]
end
end
This reads the JSON file and parses it into a Ruby object immediately. There is no special magic or anything else that happens with the file, it's opened, read, closed, and its content is passed to the JSON parser and assigned to json_object.
Once parsed, the CSV file is opened and a header row is written. It could have been written as part of the open statement but this is clearer for explaining what's going on.
json_object is a hash, so to access the 'User' data you have to use a normal hash access json_object['User']. The value for the User key is an array of hashes, so those need to be iterated over, which is what json_object['User'].each does, passing the hash elements of that array into the block as user_hash.
Inside that block it's pretty much the same thing as access the value for 'User', each "element" is a key/value pair, except 'Account' which is an embedded hash.
Read the error message. each called on a hash is giving you a sequence of arrays with two members (the key and value together). There is no values method on an array. And in any case if what you have is a hash there seems little point cycling through it with each; if you want the "User" entry in the hash, why don't you ask for it up front?
Just for posterity and context this is the script I ended up using in its entity. I needed to pull from a url, and process the results and move them to a simple CSV. I needed to wite the student id, first name, last name, and the score from each of 4 assessments to the csv.
require 'csv'
require 'json'
require 'curb'
c = Curl::Easy.new('myURL/m/v3/results')
c.http_auth_types = :basic
c.username = 'myusername'
c.password = 'mypassword'
c.perform
json_object = JSON.parse(c.body_str)
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
csv << %w[UserID FirstName LastName LifeFactors PersonalAttributes TechComp TechKnowledge]
json_object['User'].each do |user_hash|
csv << [
user_hash['UserId'],
user_hash['FirstName'],
user_hash['LastName'],
user_hash['AssessmentResults'][0]['Percent'],
user_hash['AssessmentResults'][2]['Percent'],
user_hash['AssessmentResults'][3]['Percent'],
user_hash['AssessmentResults'][4]['Percent']
]
end
end

how non existing hash key returns empty string?

Here is the exact problem;
$ hash
=> {:createAuthenticationTokenRequest=>{:playerSessionID=>"111"}}
$ hash[:attributes!]
=> "" (here is the crazy result)
$ hash.class
=> Hash
$ hash.keys
=> [:createAuthenticationTokenRequest]
what is going on here? Am i not supposed to get nil for non existent hash keys ?
Detailed problem:
I am using savon to send a webservice request and getting "can't convert Symbol into Integer" error all the time, debugging the error with pry showed me that this line is getting executed as empty string which it shouldn't.
attributes = hash[:attributes!] || {}
Help me out here!
thanks in advance, cheers!
Update:
Answer for how the hash is created;
class Gyoku::Hash
def self.iterate_with_xml(hash)
xml = Builder::XmlMarkup.new
attributes = hash[:attributes!] || {}
Update2:
This is the request i am sending
request(
createAuthenticationTokenRequest: {
playerSessionID: "111"
}
)
As i mentioned before this is savon gem code that gets executed. I tried to write the question as less boring as possible, and don't get why it gets downvoted :/
here is the source code that gets debugged.
https://github.com/savonrb/gyoku/blob/master/lib/gyoku/hash.rb
i guess i deserved to be downvoted......
Don't do this in your code,
def default_request_parameters
#default_request_parameters || Hash.new('')
end
You can create a hash like this
way 1:
new_hash = Hash.new{|h,k| h[k] = ""}
new_hash['unknown_key']
it returns
=> ""
new_hash.keys
=> ['unknown_key']
this adds 'unknown_key' key to the hash.
way 2:
hash2 = Hash.new("")
hash2['unknown_key']
it returns
=> ""
but no keys are added.
hash2.keys
it returns
=> []

How to construct the 2d structure in a dynamic fashion

I iterate through all cars and its supported attributes (many attributes per car) to create a structure like this, how do I do this in a dynamic fashion.
cars = {
"honda" => {'color' => 'blue', 'type' => 'sedan'}.
"nissan" => {'color' => 'yellow', 'type' => 'sports'}.
...
}
cars.each do |car|
car_attrs = ...
car_attrs.each do |attr|
??? How to construct the above structure
end
end
Your question is not very clear... But i guess this is what you want:
cars = {}
options = {}
options['color'] = 'blue'
...
cars['honda'] = options
Is that what you were looking for?
It sounds like you may be asking for a way to create a 2-dimensional hash without having to explicitly create each child hash. One way to accomplish that is by specifying the default object created for a hash key.
# When we create the cars hash, we tell it to create a new Hash
# for undefined keys
cars = Hash.new { |hash, key| hash[key] = Hash.new }
# We can then assign values two-levels deep as follows
cars["honda"]["color"] = "blue"
cars["honda"]["type"] = "sedan"
cars["nissan"]["color"] = "yellow"
cars["nissan"]["type"] = "sports"
# But be careful not to check for nil using the [] operator
# because a default hash is now created when using it
puts "Found a Toyota" if cars["toyota"]
# The correct way to check would be
puts "Really found a Toyota" if cars.has_key? "toyota"
Many client libraries assume that the [] operator returns a nil default, so make sure other code doesn't depend on that behavior before using this solution. Good luck!
Assuming you are using something similar to ActiveRecord (but easy to modify if you are not):
cars_info = Hash[cars.map { |car| [car.name, car.attributes] }

Resources