Ruby variable subsitution in method call - ruby

Ruby noob here. Any help with a little issue I'm having would be appreciated.
I am trying to place an array into a connection string argument which is formatted as an array.
My array is as follows:
hosts = ["192.168.0.2:27017","192.168.0.3:27017"]
I need to pull the array apart and structure it like an array so that I can substitute all of the connections into the call at once. The number of hosts can vary so hence why its in an array.
hosts_mapped = hosts.map { |i| "'" + i.to_s + "'" }.join(",")
gives me "192.168.0.2:27017","192.168.0.3:27017" as a string I think... or this may have mapped it back to an array as I get an error which looks like the one below after trying to initiate a connection.
#conn = Mongo::ReplSetConnection.new([hosts_mapped], :refresh_mode => :sync, :refresh_interval => 10)
Exception `Mongo::ConnectionFailure' at gems/mongo-1.7.0/lib/mongo/util/pool_manager.rb:282 - Cannot connect to a replica set using seeds '192.168.0.2:27017
Mongo::ConnectionFailure: Cannot connect to a replica set using seeds '192.168.0.2:27017
As you can see it only seems to reference the first entry. I need to hold this array in a configuration file so this is the reason it does not go directly into the connection string above.
To me it seems I have mapped hosts_mapped back to an array, but if I puts hosts_mapped I get the string in the correct format.
"192.168.0.2:27017","192.168.0.3:27017"
A working connection string looks like:
#conn = Mongo::ReplSetConnection.new(["192.168.0.2:27017","192.168.0.3:27017"], :refresh_mode => :sync, :refresh_interval => 10)
Does anyone have any idea where I am going wrong here?
Full code to test:
#!/usr/bin/ruby -d
require "mongo"
hosts = ["192.168.0.2:27017","192.168.0.3:27017"]
hosts_mapped = hosts.map {|i| "'" + i.to_s + "'" }.join(",") #conn =
Mongo::ReplSetConnection.new([hosts_mapped], :refresh_mode => :sync,:refresh_interval => 10)

According to the docs Mongo::ReplSetConnection.new can take an array:
Mongo::ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
Since you already have an array, you can just pass it as the first parameter:
hosts = ["192.168.0.2:27017","192.168.0.3:27017"]
Mongo::ReplSetConnection.new(hosts)

you already have an array hosts = ["192.168.0.2:27017","192.168.0.3:27017"]
And if #conn = Mongo::ReplSetConnection.new(["192.168.0.2:27017","192.168.0.3:27017"], :refresh_mode => :sync, :refresh_interval => 10) works all you need to do is
#conn = Mongo::ReplSetConnection.new(hosts, :refresh_mode => :sync, :refresh_interval => 10)

Related

ngram a database file in Ruby

I am trying to ngram my database file. It works when I ngram a parsed string, but I do not know how to do the same for my database file.
I have the following code so far:
(hopefully I am in the right track)
require 'ngram'
require 'sqlite3'
ngram = NGram.new({
:size => 2,
:word_separator => " ",
:padchar => "_"
})
p ngram.parse('something')
# => ["__", "_t", "te", "es", "st", "t_", "__"]
p ngram.parse('test phrase')
db = SQLite3::Database.new("sample.db") #opens db
#ngram sample.db
Help is very much appreciated!
From the github code of ngram gem's parse method:
def parse(phrase)
words = phrase.split(#separator)
if words.length == 1
process(phrase)
else
words.map { |w| process(w) }
end
end
So, it's expecting a string object so that it can call String#split on it. That's why it works with your first example where you pass a string as an argument to the ngram.parse method.
I am not exactly sure what you want to accomplish here, but as long as you pass a string to the ngram.parse method, it would work. Or, at least, pass an argument that responds to the split method.

How do I pass a hash from commandline?

I have a ruby script that has a hash.
Example:
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
I want to add 'snake' => 'hiss'
Example:
myscript.rb --addsound "'snake' => 'hiss'"
Then in my script have it add it to animal_sound.
Example:
animal_sound.merge! 'snake' => 'hiss'
=> {"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}
Is there a way to do this?
Here is the whole script:
#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'
options = Parser.new do |p|
p.option :addsound, "add sound"
end.process!
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
if options[:add_sound]
newsound = options[:add_sound]
animal_sound.merge! newsound
end
puts animal_sound
When I run my script I get:
$ bin/myscript.rb --addsound "'snake' => 'hiss'"
bin/myscript.rb:14:in `merge!': can't convert true into Hash (TypeError)
from bin/myscript.rb:14:in `<main>'
SOLVED:
Using PSkocik's solution I got the script to work using animal, sound = options[:addsound].split(' => '); animal_sound[animal] = sound
I also used Simone Carletti's idea to simplify the CLI command. FYI it also works if I want to pass in hash format, like myscript.rb --addsound "'snake' => 'hiss'". Of course the split has to be changed back to split(' => '). I like the simpler CLI using the :.
Example:
myscript.rb --addsound snake:hiss
Final Code:
#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'
options = Parser.new do |p|
p.option :addsound, "add sound", default: ""
end.process!
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
if options[:addsound]
animal, sound = options[:addsound].split(':')
animal_sound[animal] = sound
end
puts animal_sound
Command line:
$ bin/myscript.rb --addsound snake:hiss
{"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}
I never could get the merge to work.
Each post was helpful. Thanks.
It's a good idea to keep the CLI interface detached from the underlying implementation. In fact, you may decide to switch the script in the future from Ruby to another language, and you don't really want to change the way the code is invoked.
My suggestion is to pass a serialized value, for example
myscript.rb --addsound snake:hiss
In the code, simply decompose the content and merge it.
if options[:add_sound]
animal, sound = options[:add_sound].split(":")
animal_sound.merge!(animal => sound)
end
p.option :addsound, "add sound"
^ this makes it a flag (true or false)
What you want is make it into a switch whose value is the next argument:
p.option :addsound, "add sound", default: ""
^ this makes it a switch, the string value will be assigned to options[:addsound]
newsound = options[:addsound]
^ Here you need to drop the underscore and parse the string into a hash.
Eval is evil.
For example, you could split it on ' => ' and forget about quoting:
newsound = [ options[:addsound].split(' => ') ].to_h #and then merge it
(Passing the argument like so --addsound snake:hiss and then splitting on ':' instead of ' => ' is another good option.)
^splitting on ' => ' should yield a two-member array. Here I put it into another array (arrays of two-member arrays are convertible to hashes) to make it convertible into a hash.
Or you do completely without merging and constructing another hash:
animal, sound = options[:addsound].split(' => ')
animal_sound[animal] = sound
In regards to your error
Notice the line if options[:add_sound]. That basically evaluates to if true. You are getting your error because you are setting newsound to true, and trying to merge a Boolean into a hash. To my knowledge, the .merge only works like so: hash1.merge(hash2).
Passing command line argument
Rather than passing the argument "'snake' => 'hiss'", I suggest making this a comma-delineated list, like so: "snake,hiss". From there, in your if options[:add_sound] block, you can split the string into an array, using a comma as a splitter. Finally, rather than using .merge, you can add your key:value as you normally would for any hash in Ruby. animal_sound[arr[0]] = arr[1].
Mind you, this method will work best with a single key:value pair. I am sure you can submit multiple pairs, but you would need to (by this method) split into more arrays by an additional character(like / maybe).

how non existing hash key returns empty string?

Here is the exact problem;
$ hash
=> {:createAuthenticationTokenRequest=>{:playerSessionID=>"111"}}
$ hash[:attributes!]
=> "" (here is the crazy result)
$ hash.class
=> Hash
$ hash.keys
=> [:createAuthenticationTokenRequest]
what is going on here? Am i not supposed to get nil for non existent hash keys ?
Detailed problem:
I am using savon to send a webservice request and getting "can't convert Symbol into Integer" error all the time, debugging the error with pry showed me that this line is getting executed as empty string which it shouldn't.
attributes = hash[:attributes!] || {}
Help me out here!
thanks in advance, cheers!
Update:
Answer for how the hash is created;
class Gyoku::Hash
def self.iterate_with_xml(hash)
xml = Builder::XmlMarkup.new
attributes = hash[:attributes!] || {}
Update2:
This is the request i am sending
request(
createAuthenticationTokenRequest: {
playerSessionID: "111"
}
)
As i mentioned before this is savon gem code that gets executed. I tried to write the question as less boring as possible, and don't get why it gets downvoted :/
here is the source code that gets debugged.
https://github.com/savonrb/gyoku/blob/master/lib/gyoku/hash.rb
i guess i deserved to be downvoted......
Don't do this in your code,
def default_request_parameters
#default_request_parameters || Hash.new('')
end
You can create a hash like this
way 1:
new_hash = Hash.new{|h,k| h[k] = ""}
new_hash['unknown_key']
it returns
=> ""
new_hash.keys
=> ['unknown_key']
this adds 'unknown_key' key to the hash.
way 2:
hash2 = Hash.new("")
hash2['unknown_key']
it returns
=> ""
but no keys are added.
hash2.keys
it returns
=> []

Get values from server response in 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/, "}")
)

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']

Resources