Get values from server response in ruby - ruby

I send request to server and server returns me response. If I print this response, it looks exactly as mentioned below (with array and braces). I'm new to Ruby so I have two questions:
1. To what structure should I add this response?
2. How to I get values from this response (eg value of user_id or user_status). How to get rid of quotes in value
Request code:
def userGet(user_id_or_email)
uri = URI(SRV + '/userGet')
http = Net::HTTP.new(uri.host,uri.port)
req = Net::HTTP::Post.new(uri.path)
req['bla-bla'] = 'bla-bla'
req.set_form_data('search' => user_id_or_email)
res = http.request(req)
puts(res.read_body)
end
Output of puts(res)
array (
'user_id' => 301877459,
'login' => '0301877459',
'email' => 'YS5raG96eWFfdHZhc2lsaWlAY29ycC5iYWRvby5jb20=',
'passwd' => 'cc03e747a6afbbcbf8be7668acfebee5',
'partner_id' => '105',
'user_status' => 'active',
'nickname' => 'Test',
'fullname' => 'Test',
)

As other commentors have mentioned, the first step is to determine the encoding of the response. If you can easily change the way that the data is returned by the server, you could output valid JSON and use a gem such as this. If you cannot, then an ad-hoc method for parsing responses of this type would be to define a function like this:
def parseResult(res)
# Remove the array wrapper and any leading/trailing whitespace
parsed_string = res.gsub(/^\s*array\s*\(/, "").gsub(/[\s,]*\)[\s,]*$/, "")
# Split the string into an array of key-value tuples
parsed_array = parsed_string.split(',').collect do |tuple|
tuple.split("=>").collect do |x|
x.match(/^[\s',]*([^',]*)[\s',]*$/)[1]
end
end
# Convert the array of tuples into a hash for easy access
Hash[parsed_array]
end
This is similar sawa's method, but it assumes that you cannot trust the data being returned by the server and therefore cannot use eval safely.

Not sure what that array ( ... ) means, but assuming it means a hash, you can do:
string.eval(
string
.sub(/\A\s*array\s*\(/, "{")
.sub(/\)\s*\z/, "}")
)

Related

Cannot add new value to MongoDB's BSON field using Ruby

I have a document with the field admins and am looking to add new users into this field. The value for these new users is a simple number string.
def modify_admin(identity, doc)
ip_addr = "127.0.0.1:27017"
client = Mongo::Client.new([ip_addr], :database => "camp")
if doc[0] == 'r'
doc = doc[2..-1]
client[:inventory].update_one({"name": doc}, {$push => {"admins" => identity}})
client.close
end
The collection I'm trying to add is in this line: client[:inventory].update_one({"name": doc}, {$push => {"admins" => identity}}),
However I am running into the error NilClass instances are not allowed as keys in a BSON document. (BSON::InvalidKey).
I have tried different syntax for the $push method but nothing seems to work.
My document structure is as follows, I'm using symbols as the field value.
document = {:name => build_array[1], :owner => identity, :admins => identity}
How can I add new values to the :owner field using Ruby?
$push in ruby usually means global variable. So, all you need is to wrap $push operation into parentheses:
- client[:inventory].update_one({"name": doc}, {$push => {"admins" => identity}})
+ client[:inventory].update_one({"name": doc}, {"$push" => {"admins" => identity}})
And you should be fine

How to post a URL containting curly braces and colons

I need to do a POST request for a URL containing curly braces and colons:
http://192.168.178.23/emoncms/input/post.json?json={power:200}&apikey=671b341330a7b1a4c20bf8ae7dd1faf1&time=12345677890
I tried this:
uri = URI("http://192.168.178.23/emoncms/input/post.json")
res = Net::HTTP.post_form(uri, "json" => "{power:200}", "apikey" => "671b341330a7b1a4c20bf8ae7dd1faf1", "time" => "1234567890")
But this results in:
json=%7BPVCurrent%3A3.0%7D&apikey=671b341330a7b1a4c20bf8ae7dd1faf1&time=1406144643
The service I am calling can't parse this string. How can I force ruby not to encode these values?
The URL query values have to be encoded, but you're not going about this the right way. Use a class designed to manipulate URIs:
require 'uri'
url = URI.parse('http://192.168.178.23/emoncms/input/post.json')
url.query = URI::encode_www_form(
{
'json' => '{power:200}',
'apikey' => '671b341330a7b1a4c20bf8ae7dd1faf1',
'time' => 12345677890
}
)
url.to_s # => "http://192.168.178.23/emoncms/input/post.json?json=%7Bpower%3A200%7D&apikey=671b341330a7b1a4c20bf8ae7dd1faf1&time=12345677890"
Both Ruby's built-in URI, and Addressable::URI are designed to work with URIs. Of the two, Addressable::URI is the more feature-complete.
URI::encode_www_form basically treats the hash as if its contents were the values from a form, and encodes them as a URL query. url.query = then appends that to url.

In Ruby, how to upload multiple files in single request using RESTClient

I have to upload multiple files as form request. I am using the Rest Client to post my request. I am able to upload single file but I am not sure how to add multiple files in a single request.
I searched/googled for such option and I am not finding any solution that solves my problem.
Below is my code.
It has variable argument (*yamlfile) which takes one or more files. I have to upload all the files together.
The issue now is , I am getting syntax error when I add the loop to extract the file within the payload.
my assumption is now to form this outside the payload and include it inside the payload block but I am not sure how to do it.
Can someone help me with that.
( I have tried net/http/post/multipart library too and I don't find much documents around it)
def uploadRest(endpoint,archive_file_path,,yaml_file_path,*yamlfile)
$arg_len=yamlfile.length
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
# Trying to add multiple file, but I get syntax error
yamlfile.each_with_index { |yaml, index|
:job_upload_yaml_file+index => File.new("#{yaml_file_path}/#{pmml}")
}
})
response=request.execute
puts response.code
end
uploadRest(endpoint,archive_file_path,yaml_file_path,*yamlfile)
#files=Array.new
yamlfile.each{ |yaml_file|
#files.push(File.new("#{yaml_file_path}/#{yaml_file}"))
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => { :multipart => true, :job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp", :job_upload_yaml_file => #files })
response=request.execute
end
I had a similar problem and was able to get this to work by passing an array of arrays as a requests.
file1 = File.new("#{yaml_file_path}/#{yaml_file1}", 'rb')
file2 = File.new("#{yaml_file_path}/#{yaml_file}", 'rb')
request_body = [["files", file1], ["files", file2]]
RestClient.post url, request_body, request_headers
There were two issues with your question code:
1) Attempt to add a symbol to an integer
2) Attempt to insert contents of yamlfile direct into the hash (because that is what yamlfile.each_with_index returns, as opposed to how it calls your block. The return value from the block is not used)
Both of these code issues read as if you have gained experience in HAML or another templating language, and are using structures/ideas that would work in that?
There are lots of possble solutions in Ruby, but a simple approach to build up the hash in parts, as opposed to generate it in one go with clever hash-returning routines embedded. Try something like this:
payload_hash = {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
}
# This does not use the return value from each_with_index, instead it relies
# on the block to make changes to the hash by adding new key/value pairs
yamlfile.each_with_index { |yaml, index|
# This concatenates two strings, and then converts the combined
# string into the symbol that you want
file_key = ("job_upload_yaml_file"+index.to_s).to_sym
payload_hash[file_key] = File.new("#{yaml_file_path}/#{yaml}")
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => payload_hash
)
For added code cleanliness, you could make the first two parts a separate method, and call it where it currently has payload_hash.
This should get you over current syntax hurdles. However, I have made no attempt to check whether this will allow you to upload multiple files via RESTClient.
Section1:
#params = {
"FacialImage" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\sachin.jpg'), "image/jpeg"),
"Login" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\login.txt'), "application/json")
}

Ruby: How can I have a Hash take multiple keys?

I'm taking 5 strings (protocol, source IP and port, destination IP and port) and using them to store some values in a hash. The problem is that if the IPs or ports are switched between source and destination, the key is supposed to be the same.
If I was doing this in C#/Java/whatever I'd have to create a new class and overwrite the hashcode()/equals() methods, but that seems error prone from the little I've read about it and I was wondering if there would be a better alternative here.
I am directly copying a paragraph from Programming Ruby 1.9:
Hash keys must respond to the message hash by returning a hash code, and the hash code for a given key must not change. The keys used in hashes must also be comparable using eql?. If eql? returns true for two keys, then those keys must also have the same hash code. This means that certain classes (such as Array and Hash) can't conveniently be used as keys, because their hash values can change based on their contents.
So you might generate your hash as something like ["#{source_ip} #{source_port}", "#{dest_ip} #{dest_port}", protocol.to_s].sort.join.hash such that the result will be identical when the source and destination are switched.
For example:
source_ip = "1.2.3.4"
source_port = 1234
dest_ip = "5.6.7.8"
dest_port = 5678
protocol = "http"
def make_hash(s_ip, s_port, d_ip, d_port, proto)
["#{s_ip} #{s_port}", "#{d_ip} #{d_port}", proto.to_s].sort.join.hash
end
puts make_hash(source_ip, source_port, dest_ip, dest_port, protocol)
puts make_hash(dest_ip, dest_port, source_ip, source_port, protocol)
This will output the same hash even though the arguments are in a different order between the two calls. Correctly encapsulating this functionality into a class is left as an exercise to the reader.
I think this is what you mean...
irb(main):001:0> traffic = []
=> []
irb(main):002:0> traffic << {:src_ip => "10.0.0.1", :src_port => "9999", :dst_ip => "172.16.1.1", :dst_port => 80, :protocol => "tcp"}
=> [{:protocol=>"tcp", :src_ip=>"10.0.0.1", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}]
irb(main):003:0> traffic << {:src_ip => "10.0.0.2", :src_port => "9999", :dst_ip => "172.16.1.1", :dst_port => 80, :protocol => "tcp"}
=> [{:protocol=>"tcp", :src_ip=>"10.0.0.1", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}, {:protocol=>"tcp", :src_ip=>"10.0.0.2", :src_port=>"9999", :dst_ip=>"172.16.1.1", :dst_port=>80}]
The next, somewhat related, question is how to store the IP. You probably want to use the IPAddr object instead of just a string so you can sort the results more easily.
You can use the following code:
def create_hash(prot, s_ip, s_port, d_ip, d_port, value, x = nil)
if x
x[prot] = {s_ip => {s_port => {d_ip => {d_port => value}}}}
else
{prot => {s_ip => {s_port => {d_ip => {d_port => value}}}}}
end
end
# Create a value
h = create_hash('www', '1.2.4.5', '4322', '4.5.6.7', '80', "Some WWW value")
# Add another value
create_hash('https', '1.2.4.5', '4562', '4.5.6.7', '443', "Some HTTPS value", h)
# Retrieve the values
puts h['www']['1.2.4.5']['4322']['4.5.6.7']['80']
puts h['https']['1.2.4.5']['4562']['4.5.6.7']['443']

JSON object for just an integer

Silly question, but I'm unable to figure out..
I tried the following in Ruby:
irb(main):020:0> JSON.load('[1,2,3]').class
=> Array
This seems to work. While neither
JSON.load('1').class
nor this
JSON.load('{1}').class
works. Any ideas?
I'd ask the guys who programmed the library. AFAIK, 1 isn't a valid JSON object, and neither is {1} but 1 is what the library itself generates for the fixnum 1.
You'd need to do: {"number" : 1} to be valid json. The bug is that
a != JSON.parse(JSON.generate(a))
I'd say it's a bug:
>> JSON.parse(1.to_json)
JSON::ParserError: A JSON text must at least contain two octets!
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `initialize'
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `new'
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `parse'
from (irb):7
I assume you're using this: (http://json.rubyforge.org/)
JSON only supporting objects is simply not true -- json.org also does not suggest this imo. it was derived from javascript and thus especially strings and numbers are also valid JSON:
var json_string = "1";
var p = eval('(' + json_string + ')');
console.log(p);
// => 1
typeof p
// => "number"
ActiveSupport::JSON properly understands raw value JSON:
require 'active_support/json'
p = ActiveSupport::JSON.decode '1'
# => 1
p.class
# => Fixnum
and so does MultiJson:
require 'multi_json'
p = MultiJson.load '1'
# => 1
p.class
# => Fixnum
so, as a2800276 mentioned, this must be a bug.
but as of this writing, ruby 2's JSON has quirks_mode enabled by default when using the load method.
require 'json'
p = JSON.load '1'
# => 1
p.class
# => Fixnum
The first example is valid. The second two are not valid JSON data. go to json.org for details.
As said only arrays and objects are allowed at the top level of JSON.
Maybe wrapping your values in an array will solve your problem.
def set( value ); #data = [value].to_json; end
def get; JSON.parse( #data )[0]; end
From the very basics of what JSON is:
Data types in JSON can be:
Number
String
Json Object ... (and some more)
Reference to see complete list of Json data types
Now any Json data has to be encapsulated in 'Json Object' at the top level.
To understand why is this so, you can see that without a Json Object at the top level, everything would be loose and you could only have only one of the data type in the whole of Json. i.e. Either a number, a string, a array, a null value etc... but only one.
'Json Object' type has a fixed format of 'key' : 'value' pair.
You cannot store just the value. Thus you cannot have something like {1}.
You need to put in the correct format, i.e. 'key' : 'value' pair.

Resources