saving a gravatar to activeStorage - ruby-on-rails

the following cUrl command in the console, as per gravatar instructions saves an image to the directory it is being called from
curl "https://www.gravatar.com/avatar/ZUETpSoXKBP5VXW4qQnQFIZcLpxh5Ix2?d=identicon" --output 'temp_avatar'
However, in order to avoid hitting their server, the goal is to have an action save the output via ActiveStorage
class UserPreference < ApplicationRecord
has_one_attached :identicon
and the controller action
if !current_user.user_preference.identicon.present?
puts 'identicon absent'
result = Net::HTTP.get_response(URI.parse("https://www.gravatar.com/avatar/#{current_user.virtual_qr_code}.jpg?d=identicon --output '#{current_user.id}_avatar' "))
puts result.inspect
current_user.user_preference.identicon.attach(result)
the result is a bit perplexing. The puts commands get a response, but a different one from the processing of cUrl straight out
identicon absent
#<Net::HTTPFound 302 Found readbody=true>
Could not find or build blob: expected attachable, got #<Net::HTTPFound 302 Found readbody=true>
What is mistaken in expecting the image output to be different when called via Net::HTTP?
How should this be called to save via ActiveStorage?

It looks like you've included some of the CURL command into the URL of the Net::HTTP call, and are trying to call this string as an URL
"https://www.gravatar.com/avatar/#{current_user.virtual_qr_code}.jpg?d=identicon --output '#{current_user.id}_avatar' " where you actually wanted:
"https://www.gravatar.com/avatar/#{current_user.virtual_qr_code}.jpg?d=identicon"
You cannot use --output from within the URL, but you should use the returned body of the GET request instead.
if !current_user.user_preference.identicon.present?
puts 'identicon absent'
result = Net::HTTP.get_response(URI.parse("https://www.gravatar.com/avatar/#{current_user.virtual_qr_code}.jpg?d=identicon"))
puts result.inspect #=> Net::HTTPResponse object
# result.body will return the actual content of the HTTP request, instead of the Net::HTTPResponse object.
current_user.user_preference.identicon.attach(result.body)

Related

Why do I need to check both params and request.headers?

I have an api method in a Rails controller like the following:
def login
if !request || !request.headers
render :json => {:error => I18n.t('error_must_provide_api_key_or_token')}, :status => :unauthorized and return
end
api_key = params['X-Api-Key']
if api_key.nil?
api_key = request.headers['X-Api-Key']
end
... rest of method ...
The method first checks to see if we have a request, and whether that request has headers. Then, what I want to do is check for a header variable called X-Api-Key. I first check the params hash, and, if there isn't one found there, I then check the request.headers hash.
What I don't understand is why I have to check both of these. Previously, I had:
api_key = request.headers['X-Api-Key']
This works when I'm debugging on my local machine, but it doesn't work once I push to my production server and run in production mode. Conversely, the following:
api_key = params['X-Api-Key']
Works when I push to the production server, but doesn't work when running locally.
My local machine is running MacOS, and rbenv 1.1.0 with ruby 2.4.0p0 and rails 5.1.1.
My server is Ubuntu 16.04 and running ruby 2.1.4p265 with rails 4.2.5.
Request headers are coming from the http headers.
While params are from the body of the HTTP (if its not GET method), or from the encoded url (e.g: something like http://localhost:3000/cars?a=b, then params[:a] would give 'b').
e.g (using curl, -H stands for header)
using url encoded for the params,
curl -H 'X-Api-Key: 1234' http://localhost:3000/cars?X-Api-Key=abc
or using http request body, with application/json as content-type.
curl -X GET -H 'X-Api-Key: 1234' -H "Content-Type: application/json" -d '{"X-Api-Key": "abc" }' http://localhost:3000/cars
Then, request.headers['X-Api-Key] would returns 1234
while params['X-Api-Key'] would returns abc.
So what happened here is likely, in local you are sending the X-Api-Key, through http header, while in production you are sending it through url encoded or http request body as content-type as json.
To solve this,
if you want to support both ways, to feed X-Api-Key
through http header
http encoded params or json params in the http body
Then what you wrote above make sense.
if you want to only support http header, then change your production client code, to send X-Api-Key through http request header.
if you want to only support params, then change your local code, to send X-Api-Key, through http request body or encoded url.

Upload CSV via POST in Rails

I am trying to upload a csv file to Rails and parse it into a db. I have tried using both Paw and Postman to send the http request, specifying POST, attaching the csv file, and specifying Content-Type as application/csv
The request header:
POST /skate_parks/import HTTP/1.1
Content-Type: text/csv
Host: localhost:3000
Connection: close
User-Agent: Paw/2.3.4 (Macintosh; OS X/10.11.5) GCDHTTPRequest
Content-Length: 11663
Name,Address,Suburb,Postcode,State,Business Category,LGA,Region,
Aireys Inlet Skate Park,Great Ocean Road,Aireys Inlet,3231,VIC,Skate Parks,Surf Coast,Barwon S/W, etc...
The controller skate_parks_controller.rb
def import
SkatePark.import(params[:body])
end
The model
class SkatePark < ApplicationRecord
require 'csv'
def self.import(file)
CSV.foreach("file", headers: true) do |row|
skate_park_hash = row.to_hash
skate_park = SkatePark.where(name: skate_park_hash["name"])
if skate_park.count == 1
skate_park.first.update_attributes(skate_park_hash)
else
SkatePark.create!(skate_park_hash)
end
end
end
end
The error
Started POST "/skate_parks/import" for ::1 at 2016-05-26 13:48:34 +1000
Processing by SkateParksController#import as HTML
Completed 500 Internal Server Error in 3ms (ActiveRecord: 0.0ms)
Errno::ENOENT (No such file or directory # rb_sysopen - file):
app/models/skate_park.rb:6:in `import'
app/controllers/skate_parks_controller.rb:7:in `import'
The problem is params[:body] is nil, so you're essentially calling SkatePark.import(nil). Rails doesn't put the raw POST body into params like you've apparently assumed it does.
You have two options. The better option, in my opinion, is to upload the data as multipart/form-data. Rather than putting the raw data into the POST body, you'll do the same thing a browser does when a user chooses a file in an <input type="file">, which is to say you'll encode it as form data. When you do that, you will be able to access the data through params, as described in the Form Helpers Rails Guide under "Uploading Files." (Since you apparently aren't using a form, you can skip to "What Gets Uploaded" to see how to handle the data you receive.)
To test this with Postman, follow the instructions for "form-data" under "Request body" in the Sending Requests docs, which I'll excerpt here for posterity:
multipart/form-data is the default encoding a web form uses to transfer data. This simulates filling a form on a website, and submitting it. The form-data editor lets you set key/value pairs (using the key-value editor) for your data. You can attach files to a key as well.
Your other option is to access the POST body directly via request.raw_post as described here: How to access the raw unaltered http POST data in Rails? This is not very "Railsy," however, and among other things will be harder to test.

How to I make sure Rails controller processes data posted to it as JSON?

I am setting up a url to receive webhooks from a third party API, but I am struggling with verifying my URL as required by the API. The API will post JSON data with a verification token to my url, but when I see the POST come in, Rails says that the controller is processing it as TEXT instead of JSON and it appears rails tries to convert the JSON to a hash before my controller has a chance to interact with it.
Here is what I see when the post request is made:
Processing by WebhooksController#receive as TEXT
Parameters: {"id"=>"5bb2181f-8b7d-4ba2-80a3-072818bb5310", "type"=>"webhook.verify", "created"=>"2014-09-10T22:36:59Z", "data"=>{"token"=>"CWkrAwoPITotLOPQtaiRosOVHPKiOEyh"}, "webhook"=>{"id"=>"5bb2181f-8b7d-4ba2-80a3-072818bb5310", "type"=>"webhook.verify", "created"=>"2014-09-10T22:36:59Z", "data"=>{"token"=>"CWkrAwoPITotLOPQtaiRosOVHPKiOEyh"}}}
Which since the above is not valid JSON, leads to:
JSON::ParserError (757: unexpected token at ''):
app/controllers/webhooks_controller.rb:24:in `receive'
I have tested the controller below with the Postman Chrome extension and when posting JSON similar to what I will receive it does successfully return the token in the response header.
I understand that it might be possible that the third party API is not setting the content type to "application/json", and that could cause this, but how can I ensure Rails processes the data as JSON instead of text?
Controller:
class WebhooksController < ApplicationController
def receive
raw_body = request.body.read
json = JSON.parse raw_body
# Return verification token only once for intitial webhook setup.
token = json['data']['token']
response.headers["token"]= token
render :nothing => true
end
end
Thank you!
Though I still am not fully certain how rails is working in this situation, I have found the solution to my problem. Even though the controller code posted in my question returned the results I was looking for in the Chrome Postman extension, it was giving me a JSON parse error with the API I was trying to integrate webhooks for. I instead tested locally using curl and realized that I got the same error.
Here is the curl to test locally:
curl -X POST -H "Content-type: application/json" -H "Accept: application/json" -d '{"id":"evt_REPLACEME"}' localhost:3000/your-webhook-path
And here is my updated controller code that worked:
class WebhooksController < ApplicationController
def receive
token = params[:data][:token]
response.headers["token"]= token
render :text => token
end
end
It was unclear how the token should have been returned for this particular API, and I was unsure if I should return a response in the header or return JSON or Text. Rendering text and setting the token in the header solved this for me.

405 Method not allowed on Net::HTTP request [ruby on rails]

I'm trying to verify if there is a remote url with following code:
endpoint_uri = URI.parse(#endpoint.url)
endpoint_http = Net::HTTP.new(endpoint_uri.host, endpoint_uri.port)
endpoint_request = Net::HTTP::Head.new(endpoint_uri.request_uri)
endpoint_response = endpoint_http.request(endpoint_request)
I'm still getting 405 Method not allowed. When I use Get instead Head in Net::HTTP::Head.new I'm getting 200 Success but also with whole remote document in response what results in bigger response time (0.3s => 0.9s).
Any ideas why this is happening? Thx
There's a chance that the #endpoint url you're trying to interact with doesn't support HEAD requests (which would be really weird, but still may be the case). Your code works fine for me with a handful of urls (google.com, stackoverflow.com, etc.)
Have you tried a curl request to see what it returns?
curl -I http://www.the_website_you_want_to_test.com

How to get 302 redirect location in Rails? (have tried HTTParty, Net/Http and RedirectFollower)

Hello
i am trying to get Facebook user's album's cover picture.
as it's said in the API page, it returns "An HTTP 302 with the URL of the album's cover picture" when getting:
http s://graph.facebook.com/[album_id]}/picture?access_token=blahblahblah...
documents here: http://developers.facebook.com/docs/reference/api/album
i've tried HTTParty, Net:HTTP and also the RedirectFollower class
HTTParty returns the picture image itself, and no "location" (URL) information anywhere
NET:HTTP and RedirectFollower are a bit tricky...
if i don't use URI.encode when passing the URL into the get method, it causes "bad uri" error
but if i use URI.encode to pass the encoded URI, it causes EOFError (end of file reached)
what's amazing is that i can see the location URL when using apigee's FB API
here is the redirect method which is recommended on the Net:HTTP documents:
anything should be modified? or is there any easier way to do this?
thank you!!
def self.fetch(uri_str, limit = 10)
response = Net::HTTP.get_response(URI.parse(uri_str))
case response
when Net::HTTPSuccess then response
when Net::HTTPRedirection then fetch(response['location'], limit - 1)
else
response.error!
end
end
If you don't mind using a gem, curb will do this for you. It's all about using the follow_location parameter:
gem 'curb'
require 'curb'
# http://www.stackoverflow.com/ redirects to http://stackoverflow.com/
result = Curl::Easy.http_get("http://www.stackoverflow.com/") do |curl|
curl.follow_location = true
end
puts result.body_str
This is not the only library with this feature, though.
As a note, many times you will get an invalid location in the header and it will have to be interpreted by the user agent to render it into something useful. A header like Location: / will need to be re-written before it can be fetched. Other times you will get a header like Location: uri=... and you'll have to pull out the location from there. It's really best to leave it to your library than re-write that yourself.
here is what i end up with after some trial and error:
uri_str = URI.encode(https://graph.facebook.com/[album_id]}/picture?access_token=blahblahblah...)
result = Curl::Easy.http_get(uri_str) do |curl|
curl.follow_location = false
end
puts result.header_str.split('Location: ')[1].split(' ')[0]
the returned header_str looks like
"HTTP blah blah blah Location: http://xxxxxxx/xxxx.jpg blah blah blah"
so i managed to get the URL by using 2 split()
the final result is a clean URL
also the curl.follow_location should be false so it won't return the body of that page

Resources