Ruby URI: build URI with "param[]=value" for array params - ruby

I'm trying to query an API (lichess API). And I can't get the params to be correctly taken into account, because URI formats the URL not in the expected format for array params.
The expected format is this one: curl https://explorer.lichess.ovh/lichess?ratings[]=2200&ratings[]=2500. So the ratings params is an Array of values, and the url should be using ratings[]=value.
I'm currently generating my URI like this, as per the doc:
base = "https://explorer.lichess.ovh/master"
params = {
ratings: [1600, 1800]
}
uri = URI(base)
uri.query = URI.encode_www_form(params)
But this generate the URL like this: https://explorer.lichess.ovh/master?ratings=1600&ratings=1800... which is not the expected format, and is not understood by Lichess API.
Does Ruby provide any built-in way to generate the params in the expected format, or will I need to build the URL manually (which feels like revinventing the wheel)?

Rails/ActiveSupport has a to_query method which will do what you want.
2.1.5 :010 > params.to_query
=> "ratings%5B%5D=1600&ratings%5B%5D=1800"
Also, have a look at this comment which explains why URI.encode_www_form doesn't return square brackets.

Related

Ruby: HTTP Put method

I am attempting to update the 'ip' parameter in a json object in an API.
I have the following case:
when "put"
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = Hash.new
jobj['ip'] = "1.1.1.1"
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', '/api/v1/address_data/1.json', data = jobj.to_s)
end
This does not work, but this does:
curl -X PUT http://ip:port/api/v1/address_data/1.json -d "ip=1.1.1.1"
How do I more accurately translate the curl into a Put request in Ruby? I have tried several methods I've found through google searching, but none of them have had successful results.
A few things:
You're not sending JSON in the Ruby example, it's a string representation of a Ruby hash which isn't the same. You need the JSON module or similar.
In the Ruby code you're attempting to send a JSON object (which would look like {"ip":"1.1.1.1"} and in the curl example you're sending it in application/x-www-form-urlencoded format, so they're currently not equivalent.
Also I'd look at the type of data the server expects from your requests: both Ruby and curl send a request header of Content-Type: application/x-www-form-urlencoded by default, and you're expecting to send JSON. This is why the curl example works: the data format you're using and the header matches. Note the .json in the URL shouldn't really make any difference; the header takes precedence.
Your call to send_request has you picking out the data parameter as a Python-style keyword argument. Ruby doesn't do that: what you're actually doing there is assigning a local variable in-line with the call.
So try something like this:
require 'json' # put this at the top of the file
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = {"ip" => "1.1.1.1"}
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', uri.path, JSON.dump(jobj),
{'Content-Type' => 'application/json'})
And just a friendly reminder, saying something "doesn't work" doesn't usually give enough information to people that might answer your question: try and remember to paste in error messages, stack traces, and things like that :)

how do I parse out this URL-encoded string with a JSON array using Ruby?

When I use a webhook with Mandrill and post to my Iron Worker, I get the following Raw (this is from RequestBin, as well) -- I didn't include the whole payload, just an example:
puts payload =>
mandrill_events=%5B%7B%22event%22%3A%22inbound%22%2C%22msg%22%3A%7B%22dkim%22%3A%7B%22signed%22%3Atrue%2C%22valid%22%3Atrue%7D%2C%22email%22%3A%22kaya%40hellokaya.com%22%2C%22from_email%22%3A%22example.sender%40mandrillapp.com%22%2C%22headers%22%3A%7B%22Content-Type%22%3A%22multipart%5C%2Falternative%3B+boundary%3D%5C%22_av-7r7zDhHxVEAo2yMWasfuFw%5C%22%22%2C%22Date%22%3A%22Fri%2C+10+May+2013+19%3A28%3A20+%2B0000%22%2C%22Dkim-Signature%22%3A%5B%22v%3D1%3B+a%3Drsa-
I tried to extract the value of the parameter mandrill_events using:
puts params = CGI::parse(#payload) =>
{"mandrill_events"=>["[{\"event\":\"inbound\",\"msg\":{\"dkim\":{\"signed\":true,\"valid\":true},\"email\":\"kaya#hellokaya.com\",\"from_email\":\"example.sender#mandrillapp.com\",\"headers\":{\"Content-Type\":\"multipart\\/alternative; boundary=\\\"_av-7r7zDhHxVEAo2yMWasfuFw\\\"\",\"Date\":\"Fri, 10 May 2013 19:28:20 +0000\",\"Dkim-Signature\":[\"v=1; a=rsa-sha1; c=relaxed\\/relaxed; s=mandrill; d=mail115.us4.mandrillapp.com; h=From:Sender:Subject:List-Unsubscribe:To:Message-Id:Date:MIME-Version:Content-Type; i=example.sender#mail115.us4.mandrillapp.com;
Then I am stuck. I want to extract the email value in the JSON.array.
I thought to try the following;
puts json_params = JSON.parse(params)
But I am now feeling there must be a better way....
How can I extract the elements from the JSON array in this URL-encoded string?

Ruby Sinatra and JSON objects from toodledo API 2.0

I have a small problem with receiving JSON objects. I'm using Ruby 1.9.3 and my goal is to receive my tasks from an API via RestClient and print them more or less pretty onto the page.
I created a route /test:
get '/test' do
json_ip_url = "http://api.toodledo.com/2/tasks/get.php?key=198196ae24792467eec09ac2191*****;modafter=1234567890;fields=folder,star,priority"
ip_details = RestClient.get(json_ip_url)
test = JSON.pretty_generate(ip_details) # => throws exception
end
The JSON#pretty_generate line throws an error, "only generation of JSON objects or arrays allowed". What am I doing wrong here?
Update:
I'am now able to output via pretty_generate, but what do I have to do, to get the elements of it. Here is the JSON Data, it seems to me its an Array with Objects inside of it?
[{"num":"18","total":"18"},{"id":"11980343","title":"Add some items to your todo list","modified":1391670256,"completed":0,"folder":"0","star":"0"},{"id":"11980345","title":"Visit the Settings section and configure your account","modified":1391670256,"completed":0,"folder":"0","star":"0"},{"id":"11980347","title":"Watch our tutorial videos in the Help section","modified":1391670256,"completed":0,"folder":"0","star":"1"},{"id":"12607789","title":"test","modified":1392285802,"completed":0,"folder":"0","star":"0"},{"id":"12636039","title":"My Task","modified":1392308705,"completed":0,"folder":"0","star":"0"},{"id":"12636041","title":"Another","modified":1392308705,"completed":0,"folder":"0","star":"1"},{"id":"12636143","title":"My Task","modified":1392308789,"completed":0,"folder":"0","star":"0"},{"id":"12636145","title":"Another","modified":1392308789,"completed":0,"folder":"0","star":"1"},{"id":"12636449","title":"My Task","modified":1392308950,"completed":0,"folder":"0","star":"0"},{"id":"12636451","title":"Another","modified":1392308950,"completed":0,"folder":"0","star":"1"},{"id":"12636621","title":"My Task","modified":1392309061,"completed":0,"folder":"0","star":"0"},{"id":"12636623","title":"Another","modified":1392309061,"completed":0,"folder":"0","star":"1"},{"id":"12636665","title":"My Task","modified":1392309085,"completed":0,"folder":"0","star":"0"},{"id":"12636667","title":"Another","modified":1392309085,"completed":0,"folder":"0","star":"1"},{"id":"12636733","title":"My Task","modified":1392309137,"completed":0,"folder":"0","star":"0"},{"id":"12636735","title":"Another","modified":1392309137,"completed":0,"folder":"0","star":"1"},{"id":"12637135","title":"My Task","modified":1392309501,"completed":0,"folder":"0","star":"0"},{"id":"12637137","title":"Another","modified":1392309501,"completed":0,"folder":"0","star":"1"}]
The Code I used for pretty_generate:
get '/save' do
jdata = params[:data]
response = RestClient.get 'http://api.toodledo.com/2/tasks/get.php?key=da21e24e2a00ba9d45008974aed00***;modafter=1234567890;fields=folder,star,priority', {:accept => :json}
test = JSON.parse(response)
test.to_json
output = JSON.pretty_generate(test)
puts output
RestClient#get returns the raw response as a string (and not a hash or array) when called without a block, so ip_details isn't a structure that JSON#pretty_generate knows how to handle. You need to use JSON#parse to turn the response into a hash or array first.

Ruby simple Web Service client performing a call to Solr

I need some help in doing this: I have to build the following URL in order to perform a query aganst an Apache Solr instance:
http://localhost:8080/solr/select?q=*%3A*&fq=deal_discount%3A[20+TO+*]&fq=deal_price%3A[*+TO+100]&fq={!geofilt+pt%3D45.6574%2C9.9627+sfield%3Dlocation_latlng+d%3D600}
As you can see, the URL contains 3 times the parameter named "fq". What I'm just wondering is how to use the URI.parse() method if I need to pass three times the parameter "fq" within the Hash that is the second argument of the parse() method.
Here's a simple snippet:
path = 'http://localhost:8080/solr/select'
pars = { 'fq' => 'deal_price [* TO 100]', 'fq' => '{!geofilt pt=45.6574,9.9627 sfield=location_latlng d=600}' } # This is obviously wrong!
res = Net::HTTP::post_form( URI.parse(path), pars )
The solution would be passing the full URL as a String, but I cannot find a method that provide this kind of signature.
Could you please post a simple solution to my problem? Thanks in Advance.
Thaks for your help. Yes, you're right... A get method was what I need. Anyway I had to make a little change to your code because Net:HTTP.get() threw an exception "Unknown method hostname"
uri = URI(solrUrl)
req = Net::HTTP::Get.new(uri.request_uri)
res = Net::HTTP.start(uri.hostname, uri.port) {|http|
http.request(req)
}
This solved my problem. Thanks indeed.
Your URL suggest that you should use HTTP GET to query solr while your snippet uses POST so that is one thing to change. But I think your main problem is with the parameters, a Hash may only contain one entry for a key so you can't use a Hash in this case. One easy way is to construct the URL by hand.
params_array = ['deal_price [* TO 100]',
'{!geofilt pt=45.6574,9.9627 sfield=location_latlng d=600}']
base_url = "http://localhost:8080/solr/select"
query_string = "?fq=#{params_array.join('&fq=')}"
url = base_url + query_string
result = Net::HTTP.get url
A bit compact maybe - a more readable version may be (according to taste):
params_array = ['deal_price [* TO 100]',
'{!geofilt pt=45.6574,9.9627 sfield=location_latlng d=600}']
url = "http://localhost:8080/solr/select&"
params_array.each do |param|
url << "&fq=#{param}"
end
result = Net::HTTP.get url

Post nested params (hash) using HTTPClient

I needed a to have a multipart http post from one app to another that included a file attachment and a nested params hash. I tried using HTTPClient which worked for the file attachment, however I could not get params to send in a nested format.
data_params = Hash.new
data_params[:params] = Hash.new
data_params[:params][:f] = Hash.new
data_params[:params][:d] = Hash.new
data_params[:params][:d][:name] = "Mich"
data_params[:params][:d][:city] = "Ostin"
data_params[:params][:f][:event] = "Sundance"
http_client = HTTPClient.new
body = data_params[:params]
response = http_client.post('http://localhost:3030/receiver/receive_test_data/', body)
with the receiver app seeing the params as {"d"=>"nameMichcityOstin","f"=>"eventSundance"} (with the hash collapsed into strings on the nested level)
I wonder if this is a limitation on http posts or am I simply doing something wrong. I have worked with JSON before, which I know supports a nested structure, but there I have no idea how to add file attachments. I appreciate any suggestions or alternative methods that would comply with 'best practices' on doing something like this.
If using Rails:
> {:a=>53,:b=>{:c=>7}}.to_query
=> "a=53&b[c]=7"
http://apidock.com/rails/ActiveSupport/CoreExtensions/Hash/to_query
I'm not sure which HTTPClient library you're using so I haven't been able to try this, but what if you use keys like this
data_params[:params]['d[name]'] = "Mich"
data_params[:params]['d[city]'] = "Ostin"
i.e. data_params[:params] is just a one level hash.
and then the receiving application will unpack this into the nested hash that you expect.

Resources