Using parsed response in separate GET call - ruby

I'm new to Ruby and API, so my apologies if this is super simple...
I need to have script that will first POST to initiate the creation of an export file, and then have a GET call to retrieve the file. The GET call needs to use part of the POST json response.
I'm using the httparty gem.
I think I need to create a variable that equals the parsed json, and then make that variable part of the GET call, but I'm not clear on how to do that.
Help is appreciated.
require 'httparty'
url = 'https://api.somewhere.org'
response = HTTParty.post(url)
puts response.parse_response
json response:
export_files"=>
{"id"=> #####,
"export_id"=> #####,
"status"=>"Queued"}}
In my GET call I need to use the export_id number in the url.
HTTParty.get('https://api.somewhere.org/export_id/####')

As described in the comments but a bit more verbose and skeleton for error:
require 'httparty'
require 'json'
url = 'https://api.somewhere.org'
response = HTTParty.post(url)
if hash = JSON.parse(response.body)
if export_id = hash[:export_files][:export_id]
post = HTTParty.post("https://api.somewhere.org/export_id/#{export_id}")
end
else
# handle error
end

Related

How can I access the raw request body in ruby CGI scripts?

In a ruby script that I run as a CGI program, I need to access the body of a HTTP POST request. The request body contains JSON data:
{"data":"a"}
I want to take the whole body and parse it with JSON.parse to process it. What's the canonical way to do this? The Ruby docs don't mention the request body.
I only found a hint in a blog post that
CGI tries to parse the request body as form parameters so a blob of JSON awkwardly ends up as the one and only parameter key.
This approach seems to work
puts cgi.params.keys.first # prints {"data":"a"}
but fails as soon as the value for data is a base64 encoded string that contains an = for padding: Using this body
{"data":"a="}
results in the following output (characters missing at the end):
puts cgi.params.keys.first # prints {"data":"a
What's the correct approach to solve this?
As you might already know, when parameters and their values are urlencoded they are delimited with an =: name=Theo&language=ruby and so on.
This is why the name of the first parameter stops at the character before the =. The approach of using the first key, as describe in that blog post, isn't really reliable.
Instead, in a CGI script you can read the request body directly from stdin e.g.
request_body = $stdin.read
Note, when you instantiate a CGI object it will read in everything from stdin and attempt to parse it into the params hash.
This means that if you'd still like to use the cgi library for building your response you'll need to read from stdin earlier in the code, before creating the CGI object. e.g.
# minimal example that just outputs the request body
require 'cgi'
request_body = $stdin.read
cgi = CGI.new
cgi.out("status" => "OK", "type" => "text/plain", "connection" => "close") do
request_body
end
Apparently there is no easy solution for this in Ruby.
But there are two ways you can achieve this.
Redefine CGI::parse(params) method.
This method in CGI module is responsible for parsing both POST and GET parameters into params hash. You can redefine this method in your code so that it add an extra parameter called RAW_DATA in params hash.
def CGI::parse(query)
params = {}
query.split(/[&;]/).each do |pairs |
key, value = pairs.split('=', 2).collect {
| v | CGI::unescape(v)
}
next unless key
params[key] || = []
params[key].push(value) if value
end
#Add RAW_DATA to params
params[:RAW_DATA] = query
params.default = [].freeze
params
end
Use $stdin.read() before creating CGI instance.
But this may prevent you from making use of other CGI features.
So you may replace $stdin temporarily with a StringIO object.
require 'cgi'
require 'stringio'
raw_data = $stdin.read()
real_stdin = $stdin
$stdin = StringIO.new(raw_data)
STDIN = $stdin
cgi = CGI.new
#Your CGI code here
#........
$stdin = real_stdin
STDIN = $stdin

Working with Ruby and APIs

I am pretty new to working with Ruby, especially with APIs but I've been trying to get the Darksky API to work, but I'm afraid I'm missing something obvious with how I'm using it.
Here is what I have
require 'darksky'
darksky = Darksky::API.new('my api key')
forecast = darksky.forecast('34.0500', '118.2500')
forecast
When I run this from the command line nothing happens. What am I doing wrong here?
Simply using forecast isn't going to do anything. You need to use puts at a minimum:
puts forecast
Or, see if Ruby's object pretty-printer can return something more interesting:
require 'pp'
pp forecast
Digging in further, I think their API doesn't work. Based on their examples, using a valid key and their location samples, plus the locations from their source site Forecast.io, also returns nil.
Using the REST interface directly from Forecast.io's site does return JSON. JSON is very easy to work with in Ruby, so it's a good way to go.
Here's some code to test the API, and Forecast.io's REST interface:
API_KEY = 'xxxxxxxxxxxxxxxxxxx'
LOCATION = %w[37.8267 -122.423]
require 'darksky'
darksky = Darksky::API.new(API_KEY)
forecast = darksky.forecast(*LOCATION)
forecast # => nil
brief_forecast = darksky.brief_forecast(*LOCATION)
brief_forecast # => nil
require 'json'
require 'httparty'
URL = "https://api.forecast.io/forecast/#{ API_KEY }/37.8267,-122.423"
puts URL
# >> https://api.forecast.io/forecast/xxxxxxxxxxxxxxxxxxx/37.8267,-122.423
puts HTTParty.get(URL).body[0, 80]
# >> {"latitude":37.8267,"longitude":-122.423,"timezone":"America/Los_Angeles","offse
Notice that LOCATION is 37.8267,-122.423 in both cases, which is Alcatraz according to the Forecast.io site. Also notice that the body output displayed is a JSON string.
Pass the returned JSON to the Ruby's JSON class like:
JSON[returned_json]
to get it parsed back into a Ruby Hash. Using OpenURI (because it comes with Ruby) instead of HTTParty, and passing it to JSON for parsing looks like:
body = open(URL).read
puts JSON[body]

How Do I search Twitter for a word with Ruby?

I have written code in Ruby that will display the timeline for a specific user. I would like to write code to be able to just search twitter to just find every user that has mentioned a word. My code is currently:
require 'rubygems'
require 'oauth'
require 'json'
# Now you will fetch /1.1/statuses/user_timeline.json,
# returns a list of public Tweets from the specified
# account.
baseurl = "https://api.twitter.com"
path = "/1.1/statuses/user_timeline.json"
query = URI.encode_www_form(
"q" => "Obama"
)
address = URI("#{baseurl}#{path}?#{query}")
request = Net::HTTP::Get.new address.request_uri
# Print data about a list of Tweets
def print_timeline(tweets)
tweets.each do |tweet|
require 'date'
d = DateTime.parse(tweet['created_at'])
puts " #{tweet['text'].delete ","} , #{d.strftime('%d.%m.%y')} , #{tweet['user']['name']}, #{tweet['id']}"
end
end
# Set up HTTP.
http = Net::HTTP.new address.host, address.port
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# If you entered your credentials in the first
# exercise, no need to enter them again here. The
# ||= operator will only assign these values if
# they are not already set.
consumer_key = OAuth::Consumer.new(
"")
access_token = OAuth::Token.new(
"")
# Issue the request.
request.oauth! http, consumer_key, access_token
http.start
response = http.request request
# Parse and print the Tweet if the response code was 200
tweets = nil
puts "Text,Date,Name,id"
if response.code == '200' then
tweets = JSON.parse(response.body)
print_timeline(tweets)
end
nil
How would I possibly change this code to search all of twitter for a specific word?
The easiest approach would be to use 'Twitter' gem. Refer to this Link for more information and the result type of the search results. Once you have all the correct authorization attribute in place (oAuth-Token,oAuth-secret, etc) you should be able to search as
Twitter.search('Obama')
or
Twitter.search('Obama', options = {})
Let us know, if that worked for you or not.
p.s. - Please mark the post as answered if it helped you. Else put a comment back with what is missing.
The Twitter API suggests the URI your should be using for global search is https://api.twitter.com/1.1/search/tweets.json and this means:
Your base_url component would be https://api.twitter.com
Your path component would be /1.1/search/tweets.json
Your query component would be the text you are searching for.
The query part takes a lot of values depending upon the API spec. Refer to the specification and you can change it as per your requirement.
Tip: Try to use irb (I'd recommend pry) REPL which makes it a lot easier to explore APIs. Also, checkout the Faraday gem which can be easier to use than the default HTTP library in Ruby IMO.

How to test a JSON REST API

I'm brand new to ruby (first day working with ruby) so please forgive any novice questions and lack of understanding.
I'm trying to validate the responses to http callouts.
For example, let's say the endpoint is the following:
https://applicationname-api-sbox02.herokuapp.com
And, I'm trying to authenticate a user by sending a get request like this:
get_response = RestClient.get( "https://applicationname-api-sbox02.herokuapp.com/api/v1/users",
{
"Content-Type" => "application/json",
"Authorization" => "token 4d012314b7e46008f215cdb7d120cdd7",
"Manufacturer-Token" => "8d0693ccfe65104600e2555d5af34213"
}
)
Now, I want to validate the response and do the following:
- parse the response to ensure that it is valid JSON
- do some validation and verify the JSON has the correct data (verify that id == 4 for example)
- if an error is encountered, raise an exception using the 'raise' method.
In my first feeble attempt I tried the following:
puts get_response.body
if get_response.code == 200
puts "********* Get current user successful"
else
puts "Get current user failed!!"
end
Now, this returned that getting the current user was successful, but how do I actually parse the json, verify the correct id, and raise an exception if an error occurred?
Instead of raising an exception, write a test.
A straightforward approach, using the json parser and unit test framework from the std lib:
require 'minitest/autorun'
require 'rest_client'
require 'json'
class APITest < MiniTest::Unit::TestCase
def setup
response = RestClient.get("https://applicationname-api-sbox02.herokuapp.com/api/v1/users",
{
"Content-Type" => "application/json",
"Authorization" => "token 4d012314b7e46008f215cdb7d120cdd7",
"Manufacturer-Token" => "8d0693ccfe65104600e2555d5af34213"
}
)
#data = JSON.parse response.body
end
def test_id_correct
assert_equal 4, #data['id']
end
end
Execute with ruby $filename
JSON.parse parses a JSON string into a ruby hash
Getting started with minitest
If you are using ruby 1.8, you'll need to install the json gem and either install the minitest gem, or switch to the older testunit API. If you choose the latter, then you'll need to change require 'minitest/autorun' -> require 'test/unit' and MiniTest::Unit::TestCase -> Test::Unit::TestCase
I'm a little late to the party, but I recently co-created an rspec driven framework called Airborne for just this purpose. Check it out: https://github.com/brooklynDev/airborne
here is an example from our specs so you can see how we test json api:
it 'returns charge' do
get "/charges/#{charge.id}", '', headers
expect(response.status).to eq(200)
expect(response).to match_response_schema(:charge)
expect(response).to match_json(<<-JSON)
{
"id":"{id}",
"email": "{email}",
"ip": "127.0.0.1",
"amount": 10500,
"state": "captured",
"captured_amount": 10500,
}
JSON
end
Lets look at it closely
match_response_schema(:charge)
This matcher checks that json we get in response is in general valid. We use json-schema (json schema validator) for it. Guys from Thoughtbot have a detailed guide how to use json schema validator and create own matcher in this blog post.
Understanding JSON Schema is where I got a lot of useful information on how to create schemas for JSON documents.
match_json
This is our own matcher and we have released match_json gem recently. Using it you can test structure and values of your json. Here are two great features of this matcher:
if you don't know exact values, you can use patterns like {id}, {uuid} {date_time}, etc. we have predefined patterns but you can add your own too.
you get clear failure message what is wrong with your json e.g. "5" was not found in " > array":[1,2,3]
Parsing json can be done with the json gem: http://flori.github.com/json/
Parsed json is accessed through key/value just like in javascript. You can easily verify the values and conditionally raise errors.
Raising errors is done like so:
raise "the ID was #{id} instead of 4"
And writing unit tests can be done with Test::Unit - http://www.ruby-doc.org/stdlib-1.9.3/libdoc/test/unit/rdoc/Test/Unit.html

JSON Parsing Google API Custom Search Error

I have the following code:
require 'rubygems'
require 'httparty'
require 'pp'
require 'json'
class Search
include HTTParty
format :json
end
x = Search.get('https://www.googleapis.com/customsearch/v1key=AI...&cx=013...=flowers&alt=json')
x = x.to_s
result = JSON.parse(x)
And every time I run it on the google search results that come back I get the following:
FlowerPlaces.com Delivers Fresh <b>Flowers</b> To Your Place! <br>
Order <b>Flowers</b> Online or Call 800-411-9049 for Same Day <b>Flower</b>
Delivery.", "cacheId"=>"v94CIDza4gQJ"}]}' (JSON::ParserError)
from /usr/local/lib/ruby/1.9.1/json/common.rb:148:in `parse'
from gg.rb:15:in `<main>'
Here is that same line in the string version which JSON is trying to parse:
Order <b>Flowers</b> Online or Call 800-411-9049 for Same Day <b>Flower</b>
Delivery.\", \"cacheId\"=>\"v94CIDza4gQJ\"}]}"
Now, I've tried it with multiple search quires and I'm wondering am I doing something wrong? I added to the .to_s because the json parser tries to convert the HTTParty get statement to a string and can't and then throws an error. The JSON parser appears to get all the way to the end of the string (which is everything google has returned to me) before it throws the error.
What am I missing?
You need to add .body onto the end of the x = Search.get('http://....') like so:
x = Search.get('http://....').body

Resources