HTTP post request via Ruby - ruby-on-rails

I'm very new to ruby and trying some basic stuff. When I send HTTP request to the server using:
curl -v -H "Content-Type: application/json" -X GET -d "{"myrequest":"myTest","reqid":"44","data":{"name":"test"}}" localhost:8099
My server sees JSON data as "{myrequest:myTest,reqid:44,data:{name:test}}"
But when I send the request using the following ruby code:
require 'net/http'
#host = 'localhost'
#port = '8099'
#path = "/posts"
#body = ActiveSupport::JSON.encode({
:bbrequest => "BBTest",
:reqid => "44",
:data =>
{
:name => "test"
}
})
request = Net::HTTP::Post.new(#path, initheader = {'Content-Type' =>'application/json'})
request.body = #body
response = Net::HTTP.new(#host, #port).start {|http| http.request(request) }
puts "Response #{response.code} #{response.message}: #{response.body}"
It sees it as "{\"bbrequest\":\"BBTest\",\"reqid\":\"44\",\"data\":{\"name\":\" test\"}}" and server is unable to parse it. Perhaps there are some extra options I need to set to send request from Ruby to exclude those extra characters?
Can you please help. Thanks in advance.

What you are doing on the shell produces invalid JSON. Your server should not accept it.
$echo "{"myrequest":"myTest","reqid":"44","data":{"name":"test"}}"
{myrequest:myTest,reqid:44,data:{name:test}}
This is JSON with unescaped keys and values, will NEVER work. http://jsonlint.com/
If your server accept this "sort of kind of JSON" but does not accept the second one in your example your server is broken.
My server sees JSON data as "{myrequest:myTest,reqid:44,data:{name:test}}"
Your server sees a string. When you will try to parse it into JSON it will produce an error or garbage.
It sees it as "{\"bbrequest\":\"BBTest\",\"reqid\":\"44\",\"data\":{\"name\":\" test\"}}"
No this is how it's printed via Ruby's Object#inspect. You are printing the return value of inspect somewhere and then trying to judge whether it's valid JSON - it is not, since this string you've pasted in is made to be pasted into the interactive ruby console (irb) or into a ruby script, and it contains builtin escapes. You need to see your JSON string raw, just print the string instead of inspecting it.
I think your server is either broken or not finished yet, your curl example is broken and your ruby script is correct and will work once the server is fixed (or finished). Simply because
irb(main):002:0> JSON.parse("{\"bbrequest\":\"BBTest\",\"reqid\":\"44\",\"data\":{\"name\":\" test\"}}")
# => {"bbrequest"=>"BBTest", "reqid"=>"44", "data"=>{"name"=>" test"}}

Your problem is something other than the existence of escape characters in the string. Those are not put in by the code you show, but by irb or .inspect. If you put in a simple puts #body in your code (or in a Rails context, logger.debug #body), you'll see this. Here's an irb session showing the difference:
ruby-1.9.2-p180 :002 > require 'active_support'
=> true
ruby-1.9.2-p180 :003 > json = ActiveSupport::JSON.encode({
ruby-1.9.2-p180 :004 > :bbrequest => "BBTest",
ruby-1.9.2-p180 :005 > :reqid => "44",
ruby-1.9.2-p180 :006 > :data =>
ruby-1.9.2-p180 :007 > {
ruby-1.9.2-p180 :008 > :name => "test"
ruby-1.9.2-p180 :009?> }
ruby-1.9.2-p180 :010?> })
=> "{\"bbrequest\":\"BBTest\",\"reqid\":\"44\",\"data\":{\"name\":\"test\"}}"
ruby-1.9.2-p180 :013 > puts json
{"bbrequest":"BBTest","reqid":"44","data":{"name":"test"}}
=> nil
In any case, the best way to do json encoding in Rails is not to call ActiveSupport::JSON.encode directly, but rather override as_json in your model or use the serializable_hash feature. This will make your code cleaner as well. See the top answers to this stackoverflow question for details.

Related

How do I access an API service that doesn't have a Ruby gem?

So I want to access Glot.io that has just a barebones RESTful API from my Rails App.
Glot.io requires me to create an account and generates an API key for me -- pretty standard.
At a high-level, what is the best way for me to approach this?
Create an initializer file called glot.rb and what should I include there? Just my ENV_VARS to access the API service? Or do I not need that?
Then using the docs available to CREATE a snippet, what's the best approach to actually doing that? Should I use a CURL gem like curb to re-create this POST request?
curl --request POST \
--header 'Authorization: Token 99090-7abba-12389abcde' \
--header 'Content-type: application/json' \
--data '{"language": "python", "title": "test", "public": false, "files": [{"name": "main.py", "content": "print(42)"}]}' \
--url 'https://snippets.glot.io/snippets'
If so, what might the above look like?
Ideally I would love to get a high-level overview of the approach and then some code snippets of how I might proceed.
I would create a library (either create my own gem, or if just using it here, then in /lib) using net/http.
http://ruby-doc.org/stdlib-2.3.1/libdoc/net/http/rdoc/Net/HTTP.html
This is a very basic example, and could be done a number of ways. This example will allow you to to instantiate SnippetApi and set the URL. You can then call new_snippet, which will take the URL that you supplied, and make the API call.
To continue extending, you can change def new_snippet to def new_snippet(data), and then supply the data with your call.
Note: the filename should be snippet_api.rb to ensure that the "rails magic" works for autoloading.
lib/snippet_api.rb
require 'uri'
require 'net/http'
require 'net/https'
class SnippetApi
def initialize(url, api_token = ENV['API_TOKEN'])
#url = url
#api_token = api_token
end
def url
#url
end
def api_token
#api_token
end
def new_snippet
# https://snippets.glot.io/snippets
uri = URI.parse(self.url)
post_object = {'language': 'python', 'title': 'test', 'public': false, 'files': [{'name': 'main.py', 'content': 'print(42)'}]}
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' => 'application/json', 'Authorization': "Token #{self.api_token}"})
req.body = "[ #{post_object} ]"
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
end
end
Usage
2.3.0 :001 > snippet_api = SnippetApi.new('https://snippets.glot.io/snippets')
=> #<SnippetApi:0x00000003b6bc20 #url="https://snippets.glot.io/snippets">
2.3.0 :002 > snippet_api.new_snippet
Response 404 Not Found: {"message":"Wrong auth token"}
To ensure that files in /lib are loaded, be sure to add this to application.rb:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Usage with API Token
[brian#...]$ export API_TOKEN='token from env'
2.3.0 :001 > snippet_api = SnippetApi.new('https://snippets.glot.io/snippets')
=> #<SnippetApi:0x0000000342e8e0 #url="https://snippets.glot.io/snippets", #api_token="token from env">
2.3.0 :002 > snippet_api.api_token
=> "token from env"
2.3.0 :003 > snippet_api = SnippetApi.new('https://snippets.glot.io/snippets','from init')
=> #<SnippetApi:0x000000033a60d0 #url="https://snippets.glot.io/snippets", #api_token="from init">
2.3.0 :004 > snippet_api.api_token
=> "from init"
Edit 1: Added information about loading lib files.

rails saving recognized link from text to database with rinku

I have a rails app with chat. In the chat for the messages I use rinku gem to recognize links which works well. On the top of this I would like to save the links as message.link without the rest of the text around it from the message.body.
So for example in the code below the user sent the message.body "hi there www.stackoverflow.com" and I would like to save only the "www.stackoverflow.com" as message.link. How can I do that?
view
<p><%= find_links(message.body) %></p>
controller
def find_links(message_body)
Rinku.auto_link(message_body, mode=:all, 'target="_blank"', skip_tags=nil).html_safe
end
it will appear in the DOM as:
<p>hey there http://stackoverflow.com/</p>
and will appear in the db as message.body:
"hey there http://stackoverflow.com/"
UPDATE:
messages controller
require "uri"
def create
.....
if message.save
link = URI.extract(message.body)
update_attribute(message.link = link)
end
You need regular expression to identify URLs from text. Try following regular expression:
/(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/
Working demo: http://rubular.com/r/bHQdFHZYFH
2.1.2 :001 > str = "hey hi hello www.google.com https://stackoverflow.com http://tech-brains.blogspot.in"
2.1.2 :002 > regexp = /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/
2.1.2 :003 > str.scan(regexp)
=> [["www.google.com"], ["https://stackoverflow.com"], ["http://tech-brains.blogspot.in"]]
You can use Ruby code:
2.0.0-p247 :001 > require "uri"
=> true
2.0.0-p247 :002 > URI.extract("hey there http://stackoverflow.com/")
=> ["http://stackoverflow.com/"]
Hope it helps!

How to integrate a simple bitcoin API into a Rails app

I am new to API's and backend development in general and have been trying for a few hours now to figure out how to do something simple like call the current bitcoin market price into my Rails app.
I tried referencing http://blockchain.info/ticker with the following code in my model
require 'rest-client'
require 'json'
base_url = "http://blockchain.info/ticker"
response = RestClient.get base_url
data = JSON.load response
cool = data[0]["CNY"]
#test = JSON.pretty_generate cool
and then put this in my view
<%= #test %>
I know this is way off but I'm at a loss and figured I would see if someone could provide a good resource or maybe get me going in the right direction. Many thanks
Dude, its all working good.
Replace data[0]["CNY"] with data["CNY"], thats all.
To get more handle, execute these lines 1 by 1 in irb,
Just like this,
1.9.3p385 :001 > require 'rest-client'
=> true
1.9.3p385 :002 > require 'json'
=> true
1.9.3p385 :004 > base_url = "http://blockchain.info/ticker"
=> "http://blockchain.info/ticker"
1.9.3p385 :005 > response = RestClient.get base_url
1.9.3p385 :006 > data = JSON.load response
1.9.3p385 :007 > cool = data["CNY"]
=> {"15m"=>5519.13613, "last"=>5519.13613, "buy"=>5578.16433, "sell"=>5853.54832, "24h"=>5616.47, "symbol"=>"¥"}
1.9.3p385 :008 > #test = JSON.pretty_generate cool
=> "{\n \"15m\": 5519.13613,\n \"last\": 5519.13613,\n \"buy\": 5578.16433,\n \"sell\": 5853.54832,\n \"24h\": 5616.47,\n \"symbol\": \"¥\"\n}"
1.9.3p385 :009 > p #test
"{\n \"15m\": 5519.13613,\n \"last\": 5519.13613,\n \"buy\": 5578.16433,\n \"sell\": 5853.54832,\n \"24h\": 5616.47,\n \"symbol\": \"¥\"\n}"
=> "{\n \"15m\": 5519.13613,\n \"last\": 5519.13613,\n \"buy\": 5578.16433,\n \"sell\": 5853.54832,\n \"24h\": 5616.47,\n \"symbol\": \"¥\"\n}"
I would recommend you use httparty which makes sending requests much simpler.
With regards to your example, you could do
require 'httparty'
require 'json'
base_url = "http://blockchain.info/ticker"
response = HTTParty.get(base_url)
data = JSON.parse(response.body)
data.each_pair do |ticker, stats|
pp "Ticker: #{ticker} - 15m: #{stats['15m']}"
end
Obviously I am pp (printing) out a string just to show the data. You would actually render the data in the view if you were to do a real implementation.

How to insert into RAILS db via external RESTful call

SOLVED: I had done a few things wrong, all of which involved my controller RECEIVING the data. There was not anything wrong with the methods below on SENDING the data.
1: I was not using #report.save in my reportController#create
2: I was not passing params[:report] in my controller
3: I added "skip_before_filter :verify_authenticity_token" to my applicaiton controller to stop warnings in the logs.
Solved. data insertion successful.
=====ORIG. Question Below=====
I need an external program to issue a command that inserts stuff into a Ruby on Rails database.
I understand the security implications of this, but because this application is not public facing, it is not really an issue.
This is the workflow i am looking to achieve:
REST client > RAILS > create new DB TABLE row
For purposes of example: my route.rb file contains
resources :reports
so i am able to CRUD using those routes. I just cant seem to get my rest client to work correctly.
UPDATE:
I have tried a RUBY rest client AND curl command in ONE, to no avail.
require 'rest_client'
require 'json'
hash_to_send = {:test_name => 'Fake Name', :pass_fail => 'pass',:run_id => 1111, :category => 'Fake Category'}
#formulate CURL attempt
myCommand = "curl -H 'Content-Type: application/json' -X POST http://localhost:8889/report.json -d #{hash_to_send.to_json} > deleteme.html"
#execute CURL attempt
`#{myCommand}` # RESULT--> 795: unexpected token at 'test_name:Fake Name'
#Make Ruby rest client attempt
response = RestClient.post( "http://localhost:8889/report.json",
hash_to_send.to_json,
:content_type => :json, :accept => :json
)
#debug info
puts myCommand # Returns --> {"test_name":"Fake Name","pass_fail":"pass","run_id":1111,"category":"Fake Category"}
Instead of curl in command-line, use ruby script and handle REST calls and JSON conversion by gems. For example, using rest-client gem (https://github.com/archiloque/rest-client) and standard json gem you can write:
require 'rest_client'
require 'json'
response = RestClient.post( "http://localhost:8889/report.json",
params_in_hash.to_json,
{ :content_type => :json, :accept => :json }
)

Ruby: HTTParty: can't format XML POST data correctly?

NOTE: "object" is a placeholder work, as I don't think I should be saying what the controller does specifically.
so, I have multiple ways of calling my apps API, the following works in the command line:
curl -H 'Content-Type: application/xml' -d '<object><name>Test API object</name><password>password</password><description>This is a test object</description></object>' "http://acme.example.dev/objects.xml?api_key=1234"
the above command generates the following request in the devlog:
Processing ObjectsController#create to xml (for 127.0.0.1 at 2011-07-07 09:17:51) [POST]
Parameters: {"format"=>"xml", "action"=>"create", "api_key"=>"1234", "controller"=>"objects",
"object"=>{"name"=>"Test API object", "description"=>"This is a test object", "password"=>"[FILTERED]"}}
Now, I'm trying to write tests for the actions using the API, to make sure the API works, as well as the controllers.
Here is my current (broken) httparty command:
response = post("create", :api_key => SharedTest.user_api_key, :xml => data, :format => "xml")
this command generates the following request in the testlog:
Processing ObjectsController#create to xml (for 0.0.0.0 at 2011-07-07 09:37:35) [POST]
Parameters: {
"xml"=>"<object><name><![CDATA[first post]]></name>
<description><![CDATA[Things are not as they used to be]]></description>
<password><![CDATA[WHEE]]></password>
</object>",
"format"=>"xml",
"api_key"=>"the_hatter_wants_to_have_tea1",
"action"=>"create",
"controller"=>"objects
So, as you can see, the command line command actually generates the object hash from the xml, whereas the httparty command ends up staying in xml, which causes problems for the create method, as it needs a hash.
Any ideas / proper documentation?
Current documentation says that post takes an url, and "options" and then never says what options are available
**EDIT:
as per #Casper's suggestion, my method now looks like this:
def post_through_api_to_url(url, data, api_key = SharedTest.user_api_key)
response = post("create", {
:query => {
:api_key => api_key
},
:headers => {
"Content-Type" => "application/xml"
},
:body => data
})
ap #request.env["REQUEST_URI"]
assert_response :success
return response
end
unfortunately, the assert_response fails, because the authentication via the api key fails.
looking at the very of of the request_uri, the api_key isn't being set properly... it shows:
api_key%5D=the_hatter_wants_to_have_tea1"
but it should just be equals, without the %5D (right square bracket)
I think this is how you're supposed to use it:
options = {
:query => {
:api_key => 1234
},
:headers => {
"Content-Type" => "application/xml"
},
:body => "<xmlcode>goes here</xmlcode>"
}
post("/create", options)
Forgive me for being basic about it but if you only want to send one variable as a parameter, why don't you do as Casper suggests, but just do:
post("/create?api_key=1234", options)
Or rather than testing HTTParty's peculiarities in accessing your API, perhaps write your tests using Rack::Test? Very rough example...
require "rack/test"
require "nokogiri"
class ObjectsTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
def create_an_object(o)
authorize "x", "1234" # or however you want to authenticate using query params
header 'Accept', 'text/xml'
header 'Content-Type', 'text/xml'
body o.to_xml
post "/create"
xml = Nokogiri::XML(last_response.body)
assert something_logic_about(xml)
end
end

Resources