How to take that data from api and write a function - ruby

Okay, it's been many days. I have been going back and forth with this api, and would like to get the following results.
Here is the problem.
uri = URI('https://api.wmata.com/StationPrediction.svc/json/GetPrediction/All')
uri.query = URI.encode_www_form({'api_key' => 'ihaveit',})
request = Net::HTTP::Get.new(uri.request_uri)
#response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
http.request(request)
#data = JSON.parse #response.body
Now i've the #data parsed as JSON. I am trying to write a class Trains and use the following data.
{
"Car": "6",
"Destination": "SilvrSpg",
"DestinationCode": "B08",
"DestinationName": "Silver Spring",
"Group": "1",
"Line": "RD",
"LocationCode": "A01",
"LocationName": "Metro Center",
"Min": "3"
},
Here is the code for class Trains
class Trains
def initialize(car, destination, destinationcode, destinationname, group, line, locationcode, locationname)
#car = car
#destination = destination
#destinationcode = destinationcode
#destinationname = destinationname
#group = group
#line = locationcode
#locationname = locationname
end
And now im stuck about the next step. I am totally new to api. I can write the following for a static class.
def to_s
puts(#car + #destination + #destinationcode + #destinationname + #group + #line + #locationname)
end
= Trains.new
puts Train
end
I've got this so far.
class TrainLoader < Struct.new(:car, :destination, :destinationcode, :destinationname, :group, :line, :locationcode, :locationname)
class Trains
end
t = Trains.new(#data["Car"],#data["DestinationCode"], #data[DestinationName],#data[Group],#data[Line], #data[LocationCode], #data[LocationName], #data[Min])

You already have a class, just write a to_s funtcion:
class Trains
def initialize(car, destination, destinationcode, destinationname, group, line, locationcode, locationname)
#car = car
#destination = destination
#destinationcode = destinationcode
#destinationname = destinationname
#group = group
#line = locationcode
#locationname = locationname
end
# inspect attributes with own `to_s` method
def to_s
"#{#car} #{#destination} #{#destinationcode} #{#destinationname}"
end
end
Now create an instance of your class:
>> train = Trains.new(#data["Car"], #data["Destination"], #data["DestinationCode"])..
>> train.to_s
But i can suggest you a more elegant solution, with struct:
>> data = {
?> "Car": "6",
?> "Destination": "SilvrSpg",
?> "DestinationCode": "B08",
?> "DestinationName": "Silver Spring",
?> "Group": "1",
?> "Line": "RD",
?> "LocationCode": "A01",
?> "LocationName": "Metro Center",
?> "Min": "3"
>> }
>> class Trains < Struct.new(*data.keys)
#> # create a struct with attributes like keys in data hash `"Cat"` or `"Min"`
>> end
>> t = Trains.new(*data.values)
# create instance of Trains class with values from a data hash `"6"`
=> #<struct Trains Car="6", Destination="SilvrSpg", DestinationCode="B08", DestinationName="Silver Spring", Group="1", Line="RD", LocationCode="A01", LocationName="Metro Center", Min="3">
>> t.Car
=> "6"
>>

Related

I'm having issues with using an API

I have the following code:
class EpisodeIndex::API
def initialize
#url = "https://www.officeapi.dev/api/episodes?limit=400"
end
def get_episode_data
uri = URI.parse(#url)
response = Net::HTTP.get(uri)
data = JSON.parse(response)
data["data"].each do |episode|
get_episode_title(episode["title"])
end
end
def get_episode_title(title)
uri = URI.parse(title)
response = Net::HTTP.get(title)
data = JSON.parse(response)
binding.pry
end
EpisodeIndex::API.new.get_episode_data
end
and I'm getting this error in return.
`get_response': undefined method `hostname' for "Pilot":String (NoMethodError)
jocelynpeters#Jocelyns-Air office_cli %
I have no idea how to fix it. Be kind, please. I'm very new to programming.
Thanks!
The data return by the API looks like this:
{
"data":
[
{
"_id":"5e94d646f733a1332868e1dc",
"title":"Pilot",
"description":"A documentary crew gives a firsthand introduction to the staff of the Scranton branch of the Dunder Mifflin Paper Company, managed by Michael Scott.",
"writer": {
"_id":"5e95242f9511994a07f9a319",
"name":"Greg Daniels",
"role":"Writer/Director",
"__v":0
},
"director": {
"_id":"5e9523649511994a07f9a313",
"name":"Ken Kwapis",
"role":"Director",
"__v":0
},
"airDate":"2005-03-24T06:00:00.000Z",
"__v":0
},
# ...
That means the result set already includes the title of each episode and it doesn't include any URL at which you could load further information (what you currently try in your get_episode_title method). Therefore you can simplify your code to:
module EpisodeIndex
require "json"
require "net/http"
class API
def initialize
#url = "https://www.officeapi.dev/api/episodes?limit=400"
end
def titles
uri = URI.parse(#url)
response = Net::HTTP.get(uri)
data = JSON.parse(response)
data["data"].map do |episode|
episode["title"]
end
end
end
end
EpisodeIndex::API.new.titles
#=> ["Pilot", "Diversity Day", "Health Care", "The Alliance", "Basketball", "Hot Girl", "The Dundies", "Sexual Harassment", "Office Olympics", "The Fire", "Halloween", "The Fight", "The Client", "Performance Review", "E-Mail Surveillance", "Christmas Party", "Booze Cruise", "The Injury", "The Secret", "The Carpet", "Boys and Girls", "Valentine's Day", "Dwight's Speech", "Take Your Daughter to Work Day", "Michael's Birthday", "Drug Testing", "Conflict Resolution", "Casino Night"]

Nested json from ruby hash

Trying to build a json out of Ruby hash
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each do |key, value|
temp_rides["rides"].push({"key" => "value"})
puts temp_rides
end
apparently it fails with undefined methodpush'` I believe I need to load the json object before append (based on my python background).
I am looking for a output something like this
{
"rides": {
"lyft": {
"type": "car"
},
"scoot": {
"type": "scooter"
},
"blade": {
"type": "helicopter"
}
}
}
This is an answer to the OP's original question. I have no interest in changing it to attempt to answer a moving target.
Code
def doit(rides)
{ rides: rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }.
map { |a| hashify(a.flatten.drop(1)) }.
reduce(&:merge) }
end
def hashify(arr)
f,*rest = arr
return f if rest.empty?
{ f.to_sym=>hashify(rest) }
end
Examples
Here is an example of the use of the (recursive) helper method hashify:
hashify ["lyft", :type, "car"]
#=> {:lyft=>{:type=>"car"}}
We are given the hash rides:
rides = {:ride1=>"lyft", :type=>"car", :ride2=>"Scoot",
:type2=>"scooter", :ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>"car"},
# :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}}
Let's add some more key-value pairs to rides:
rides = {:ride1=>"lyft", :type=>"car", :color=>"blue",
:ride2=>"Scoot", :type2=>"scooter", :make=>"Vespa", :model=>"98",
:ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>{:car=>{:color=>"blue"}}},
# :Scoot=>{:type2=>{:scooter=>{:make=>
# {:Vespa=>{:model=>"98"}}}}},
# :blade=>{:type3=>"helicopter"}}}
Explanation
The steps for the first example are as follows.
enum = rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }
#=> #<Enumerator: #<Enumerator::Generator:0x00005a49d68217f0>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[[:ride1, "lyft"], [:type, "car"]],
# [[:ride2, "Scoot"], [:type2, "scooter"]],
# [[:ride3, "blade"], [:type3, "helicopter"]]]
Continuing,
a = enum.map { |a| hashify(a.flatten.drop(1)) }
#=> [{:lyft=>{:type=>"car"}},
# {:Scoot=>{:type2=>"scooter"}},
# {:blade=>{:type3=>"helicopter"}}]
h = a.reduce(&:merge)
#=> {:lyft=>{:type=>"car"}, :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}
{ rides: h }
#=> <as above>
It gave you undefined method push because there is no such method for a hash.
temp_rides = {"rides": {}}
# This create a hash with `:rides` symbol as the key {:rides=>{}}
# To push an object into a hash. Use operator[]= or #store method
temp_rides[:rides][:key1] = 'value1'
temp_rides[:rides].store(:key2, 'value2')
A working example:
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each_pair do |k, v|
temp_rides[:rides][k] = {:type => v}
end
puts JSON.pretty_generate(temp_rides)
This is actually pretty easy to do with a simple transform:
def rejig(rides)
rides.map do |name, type|
[ name, { type: type } ]
end.to_h
end
Where that will rework your structure into the desired shape.
Using it is easy:
require 'json'
rides = { lyft: "car", scoot: "scooter", blade: "helicopter" }
puts JSON.dump(rejig(rides))
# => {"lyft":{"type":"car"},"scoot":{"type":"scooter"},"blade":{"type":"helicopter"}}
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
temp_rides[:rides].merge!(rides)
to get ruby hash output
temp_rides
to get json format output
JSON.dump temp_rides
require 'json'
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
transformed = {
'rides' => rides.inject({}) { |h,(k,v)| h[k] = { 'type' => v }; h }
}
JSON.dump(transformed)

Parse json to ruby object

In ruby how can i parse a json to an array of objects?
Example: i have 2 classes:
class Person
attr_accessor :name, :address, :email, :address
end
And:
class Address
attr_accessor :street, :city, :state, :person
end
When i make a request i get the following json:
{
"data": [
{
"id": 9111316,
"name": "Mason Lee",
"email": "normanodonnell#biospan.com",
"address": {
"state": "American Samoa",
"street": "Cameron Court",
"city": "Wakulla"
}
},
{
"id": 500019,
"name": "Stella Weeks",
"email": "hansenwhitfield#candecor.com",
"address": {
"state": "Nevada",
"street": "Lake Street",
"city": "Wacissa"
}
}
]
}
This json should be parsed into an array of Person.
For now i'm doing:
#json gem
require 'json'
#...
#parse the json and get the 'data'
parsed_json = JSON.parse json
json_data = parsed_json['data']
objects = Array.new
if json_data.kind_of?(Array)
#add each person
json_data.each { |data|
current_person = Person.new
data.each { |k, v|
current_person.send("#{k}=", v)
}
objects.push(current_person)
}
end
#return the array of Person
objects
I have a lot of objects like the above example and do this parse manually is not desirable. There is an automated way to do this?
By "automated way" i mean something like in java with jackson:
ObjectMapper mapper = new ObjectMapper();
List<Person> myObjects = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
You can initialize the Person with the hash:
json_data = JSON.parse(json)['data']
json_data.map do |data|
Person.new data
end
class Person
attr_accessor :name, :email, :address
def initialize params
params.each { |k,v| klass.public_send("#{k}=",v) }
end
end
If you want to choose the class dynamically, you can use:
json_data.map do |data|
klass = 'Person'
klass.get_const.new data
Why not just make the method yourself? Example:
require 'json'
def parse_json_to_class_array(data,root_node,to_klass)
json_data = JSON.parse(data)[root_node]
if json_data.is_a?(Array)
objects = json_data.map do |item|
klass = to_klass.new
item.each { |k,v| klass.public_send("#{k}=",v) }
klass
end
end
objects ||= []
end
Then for your example you could call it like so
json ="{\"data\":[
{\"id\":9111316,
\"name\":\"Mason Lee\",
\"email\":\"normanodonnell#biospan.com\",
\"address\":{
\"state\":\"American Samoa\",
\"street\":\"Cameron Court\",
\"city\":\"Wakulla\"
}
},
{\"id\":500019,
\"name\":\"Stella Weeks\",
\"email\":\"hansenwhitfield#candecor.com\",
\"address\":{
\"state\":\"Nevada\",
\"street\":\"Lake Street\",
\"city\":\"Wacissa\"
}
}
]
}"
class Person
attr_accessor :id, :name,:email, :address
end
parse_json_to_class_array(json,'data',Person)
#=>[#<Person:0x2ede818 #id=9111316, #name="Mason Lee", #email="normanodonnell#biospan.com", #address={"state"=>"American Samoa", "street"=>"Cameron Court", "city"=>"Wakulla"}>,
#<Person:0x2ede7a0 #id=500019, #name="Stella Weeks", #email="hansenwhitfield#candecor.com", #address={"state"=>"Nevada", "street"=>"Lake Street", "city"=>"Wacissa"}>]
Obviously you can expand this implementation to support single objects as well as overwrite Person#address= to perform the same operation and turn the address Hash into an Address object as well but this was not shown in your example so I did not take it this far in my answer.
A more dynamic example can be found Here

I have a class with some parameters that I'd like to be filled using data from a JSON file

I am trying to create a monopoly manager for fun and I have the data for all the tiles in a JSON file which I'd like to use to iterate over and use for the data. I currently have this code:
require 'json'
file = File.read('monopoly-data.json')
data_hash = JSON.parse(file)
data = data_hash['properties'].sort_by{ |e| e['id'].to_i }
class Property
attr_accessor :id, :group, :colour, :name, :price, :rent, :house_price, :mortage
def initialize(params = {})
#id = params[:id]
#group = params[:group]
#colour = params[:colour]
#name = params[:name]
#price = params[:price]
#rent = params[:rent]
#house_price = params[:house_price]
#mortage = params[:mortage]
end
end
And the JSON file is in this gist.
I haven't been able to figure out how to use the JSON data to make more properties, I have played around with for loops on the data, data.each trying to make something like this automatically:
parkveien = Property.new(
id: 1,
group: 'property',
colour: 'brown',
name: 'Parkveien',
price: 1200,
rent: [40, 200, 600, 1800, 3200, 5000],
house_price: 1000,
mortage: 600
)
However I just can't wrap my head around how to do that, any help would be highly appreciated!
You can use each to generate the Property objects and collect them into an array.
properties = []
data.each { |property| properties << Property.new(property) }
You can then access individual properties from the array by using select as needed.
properties.select { |property| property.name == "Parkveien" }
Also, since the keys in your data hash are strings, you need to change your initialize to use strings instead of symbols:
def initialize(params = {})
#id = params["id"]
#group = params["group"]
#colour = params["colour"]
#name = params["name"]
#price = params["price"]
#rent = params["rent"]
#house_price = params["house_price"]
#mortage = params["mortage"]
end

parse json to object ruby

I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.

Resources