RoR Net::HTTP post error undefined method `bytesize' - ruby-on-rails

I am currently banging my head against the wall repeatedly until I get passed this issue. I'm using ruby-1.9.3-p194 and Rails. I'm attempting to make a post request which I can do fine with Net::HTTP.post_form, but I can't use that here because I need to set a cookie in the header. http.post is erroring saying
"undefined method `bytesize' for #<Hash:0xb1b6c04>"
because I guess it's trying to perform some operation on the data being sent.
Does anyone have some kind of fix or work around?
Thanks
headers = {'Cookie' => 'mycookieinformationinhere'}
uri = URI.parse("http://asite.com/where/I/want/to/go")
http = Net::HTTP.new(uri.host, uri.port)
response = http.post(uri.path, {'test' => 'test'}, headers)

The bytesize method is on String, not Hash. That's your first clue. The second clue is the documentation for Net::HTTP#post:
post(path, data, initheader = nil, dest = nil)
Posts data (must be a String) to path. header must be a Hash like { ‘Accept’ => ‘/’, … }.
You're trying to pass a Hash, {'test' => 'test'}, to post where it expects to see a String. I think you want something more like this:
http.post(uri.path, 'test=test', headers)

Related

How to correctly replicate response body of an Octokit requests' response for webmock stub

Octokit responses are of type Sawyer::Response
They look like this:
{:name=>"code.py",
:content => "some content"}
I am trying to stub my request like so
reponse_body = {:content => "some content"}
stub_request(:any, /.*api.github.com\/repos\/my_repo\/(.*)\/code.py/).to_return(:status => 200, :body => response_body)
In my code I then call response.content, so I want to be able to get the content from the response.
I currently get the error: 'WebMock::Response::InvalidBody: must be one of: [Proc, IO, Pathname, String, Array]. 'Hash' given'. What is the proper format for response_body? If I turn it into a json, I then can't do response.content on the object in my code.
You are passing a hash as the expected response and Webmock doesn’t know what that should be encoded to (see this Webmock issue). As you mentioned, you could use response_body.to_json, however you would then be unable to use the dot notation to access data.
Since you’re using RSpec, I’d make use of Test Doubles to pretend you have a Sawyer::Resource object:
response_body =
[
double("Sawyer::Resource",
{
:name=>"code.py",
:content => "some content"
})
]
You should then be able to access data using the dot notation like you would with the actual response.
You need to provide the JSON body as a string, and an appropriate Content-Type header. E.g., to stub the call
Octokit::Client.new.user(user_login)
you need something like
stub_request(:get, "https://api.github.com/users/#{user_login}")
.to_return(
status: 200,
body: user_json, # a string containing the JSON data
headers: { content_type: 'application/json; charset=utf-8' }
)
(If you don't provide the Content-Type header, Octokit won't try to parse the JSON and you'll just get the raw string back.)
If you look at the Octokit source you can see how they use Webmock in their own tests. (The json_response() method called in that test is in helper.rb.)
I had this exact problem, and in the end solved it by stubbing out the Octokit client. In order to check the test coverage within Octokit, I followed instructions here.
Octokit requests are all tested with VCR, so assuming you are happy with their test coverage, it is reasonably safe to stub Octokit::Client within your application.
In case if someone is still confused, here is a complete sample how Octokit call could be tested with rspec.
Method:
require 'octokit'
def get_user_name(login)
Octokit.configure { |c| c.api_endpoint = 'https://git.my_company.com/api/v3/' }
client = Octokit::Client.new(:access_token => 'my_token')
response = client.user(login)
return response.name
end
Test:
describe '#get_user_name' do
it 'should return name' do
response_body = {:name => "James Bond"}
stub_request(:get, "https://git.my_company.com/api/v3/users/bondj").
to_return(status: 200,
body: JSON.generate(response_body),
headers: { content_type: 'application/json; charset=utf-8' })
result = subject.send(:get_user_name, 'bondj')
expect(result).to eq('James Bond')
end
end

How to pass patch http request parameters via HTTParty gem?

I am using the HTTParty gem to make a patch request to update an object via an API call as follows:
params = { first_name: "John"}
#options = {params: params}
#response = HTTParty.patch("http://localhost:3000/1/kites/4", #options)
But on the API side, within the update method that the above PATCH request is supposed to call I only see the following parameters available:
{"format"=>"json",
"controller"=>"api/v1/kites",
"action"=>"update",
"version"=>"1",
"id"=>"4"}
An error message is passed back to the #response for pasrsing.
What happened to first_name and/or how do I call HTTParty.patch appropriately if that is indeed what is causing the loss of the parameters passed to the API?
EDIT:
As it turns out, if I do
#options = {query: params}
that will work but only if I keep a query under a certain size....
Not sure what your 'patch' action does exactly in your API but the documentation says that you need to pass a URL and params in a body: key, like so:
HTTParty.patch('site/your_url', body: { key1: value, key2: value })
Alternalitvely, you can pass the params in query: key which appends the params to the URI.
HTTParty.patch('site/your_url', query: { key1: value, key2: value })

Parsing JSON in Controller w/ HTTParty

In my controller I have the following code...
response = HTTParty.get('https://graph.facebook.com/zuck')
logger.debug(response.body.id)
I am getting a NoMethodError / undefined method `id'
If I do...
logger.debug(response.body)
It outputs as it should...
{"id":"4","name":"Mark Zuckerberg","first_name":"Mark","last_name":"Zuckerberg","link":"http:\/\/www.facebook.com\/zuck","username":"zuck","gender":"male","locale":"en_US"}
One would think it's response.body.id, but obviously that's not working. Thanks in advance!
Try this:
body = JSON.parse(response.body)
id = body["id"]
For this kind of thing, I'd recommend either a) using Koala or b) create a class using httparty. You can then set format: json to auto parse the returned json. See here and here
You can force the response to be treated as JSON using HTTParty.get like so:
response = HTTParty.get("http://itunes.apple.com/search",
{query: {term: 'tame impala'}, format: :json})
response['results'][0]['trackName']
=> "Let It Happen"
You can use response['id'] in case that the response Content-Type is application/json or also response.parse_response to get a Hash generated from the JSON payload.
response = HTTParty.get('https://graph.facebook.com/zuck')
payload = response.parsed_response
logger.debug(payload['id'])
Note: parsed_response is a Hash, only if the response Content-Type is application/json, otherwise HTTParty will return it as a string. For enforcing a Hash, in case the response does not return application/json, you can pass the format as a parameter HTTParty.get(url, format: :json).
HTTParty should automatically parse the content based on the content type returned. Something fishy seems to be going on with zuck's json.
pry(main)> HTTParty.get('https://graph.facebook.com/zuck')
=> "{\"id\":\"4\",\"first_name\":\"Mark\",\"gender\":\"male\",\"last_name\":\"Zuckerberg\",\"link\":\"https:\\/\\/www.facebook.com\\/zuck\",\"locale\":\"en_US\",\"name\":\"Mark Zuckerberg\",\"username\":\"zuck\"}"
But this works OK:
pry(main)> HTTParty.get('http://echo.jsontest.com/foo/bar/baz/foo')
=> {"baz"=>"foo", "foo"=>"bar"}
Don't forget to require 'httparty' if you're trying that in the console yourself.

Why does it say I have the wrong number of arguments with RestClient Delete Method?

I have been using RestClient to call the API, so here I am calling the DELETE method to delete one image:
#mposter_D = params[:mposter_D]
#mid_D = params[:mid_D]
req = Hash.new
req['mov'] = #mid_D
puts "....#{req.to_json}"
resource_pos = RestClient::Resource.new
Rails.application.config.sta_url+"/movi/pos/"+#mposter_D
response_pos = resource_pos.delete req.to_json, :content_type => :json
After calling this I am getting this:
ArgumentError (wrong number of arguments (2 for 1)): in the last line of code
I was having the same problem. Apparently you cannot pass parameters from within the url when doing a delete method. (i.e. This will NOT work: http://example.com/resource?foo=bar&baz=qux)
If you need to add parameters to your delete request use the following format:
RestClient.delete 'http://example.com/resource', :params => {:foo => 'bar', :baz => 'qux'}
See the restclient docs section called Query Parameters
All I know about delete via rest_client is that, it takes only one parameter :
RestClient.delete 'http://demo.com/your_resourse'
you have to pass your url in that method. Thanks
Try this:
RestClient.delete 'http://foo.com/your_resourse_id?#{req.to_query}'
Or:
RestClient.delete 'http://foo.com/your_resourse_id?mov=' + #mid_D

rails params hash to String:String hash

I am doing an http get using the url http://localhost/add?add_key[0][key]=1234&add_key[0][id]=1.
I have a rails app which gives me a neat params hash {"add_key"=>{"0"=>{"key"=>"1234", "id"=>"1"}}. However when I try to post this to a different server using
new_uri = URI.parse("http://10.10.12.1/test")
res = Net::HTTP.post_form new_uri,params
The server handling the post is seeing this parameter in the request
{"add_key"=>"0key1234id1"}
Looks like post_form requires a String to String hash. So how do I convert the params hash to
{"add_key[0][key]" => "1234", add_key[0][id]" => "1"}
From the fine manual:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
So you're right about what params needs to be.
You could grab the parsed params in your controller:
{"add_key"=>{"0"=>{"key"=>"1234", "id"=>"1"}}
and then recursively pack that back to the flattened format that post_form expects but that would be a lot of pointless busy work. An easy way to do this would be to grab the raw URL and parse it yourself with URI.parse and CGI.parse, something like this in your controller:
u = URI.parse(request.url)
p = CGI.parse(u.query)
That will leave you with {"add_key[0][key]" => "1234", "add_key[0][id]" => "1"} in p and then you can hand that p to Net::HTTP.post_form.

Resources