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

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

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 do you parse this string?

/events/3122671255551936/?ref=br_rs&action_history=null
I would just like to extract the number after '/events/' and before '/?ref=br_rs...
\
You could split it by the / character:
irb(main):003:0> "/events/3122671255551936/?ref=br_rs&action_history=null".split("/")[2]
=> "3122671255551936"
You can also use String#scan method to grab the digits:
"/events/3122671255551936/?ref=br_rs&action_history=null".scan(/\d+/).join
# => "3122671255551936"
If your string is str:
x = str["/events/".size..-1].to_i
#=> 3122671255551936
If you want the string:
x.to_s
#=> "3122671255551936"
You're looking at the path from a URL. A basic split will work initially:
str = '/events/3122671255551936/?ref=br_rs&action_history=null'
str.split('/')[2] # => "3122671255551936"
There are existing tools to make this easy and that will handle encoding and decoding of special characters during processing of the URL:
require 'uri'
str = '/events/3122671255551936/?ref=br_rs&action_history=null'
scheme, userinfo, host, port, registry, path, opaque, query, fragment = URI.split(str)
scheme # => nil
userinfo # => nil
host # => nil
port # => nil
registry # => nil
path # => "/events/3122671255551936/"
opaque # => nil
query # => "ref=br_rs&action_history=null"
fragment # => nil
uri = URI.parse(str)
path accesses the path component of the URL:
uri.path # => "/events/3122671255551936/"
Making it easy to grab the value:
uri.path.split('/')[2] # => "3122671255551936"
Now, imagine if that URL had a scheme and host like "http://www.example.com/" prepended, as most URLs do. (Having written hundreds of spiders and scrapers, I know how easy it is to encounter such a change.) Using a naive split('/') would immediately break:
str = 'http://www.example.com/events/3122671255551936/?ref=br_rs&action_history=null'
str.split('/')[2] # => "www.example.com"
That means any solution relying on split alone would break, along with any others that try to locate the position of the value based on the entire string.
But using the tools designed for the job the code would continue working:
uri = URI.parse(str)
uri.path.split('/')[2] # => "3122671255551936"
Notice how simple and easy to read it is, which will transfer to being easier to maintain. It could even be simplified to:
URI.parse(str).path.split('/')[2] # => "3122671255551936"
and continue to work.
This is because URL/URI are an agreed-upon standard, making it possible to write a parser to take apart, and build, a string that conforms to the standard.
See the URI documentation for more information.

Ruby find key by name inside converted JSON array of hashes

I have a Ruby hash converted from JSON data, it looks like this:
{ :query => {
:pages => {
:"743958" => {
:pageid => 743958,
:ns => 0,
:title => "Asterix the Gaul",
:revisions => [ {
:contentformat => "text/x-wiki",
:contentmodel => "wikitext",
:* => "{{Cleanup|date=April 2010}}\n{{Infobox graphic novel\n<!--Wikipedia:WikiProject Comics-->...
All the good stuff is inside the revisions array and then the Infobox hash.
The problem I have is getting to the Infobox hash. I can't seem to get to it. The pages and pageid hashes might not exist for other entries and of course the ID would be different.
I've tried all sorts of methods I could think of like .map, .select, .find, .include?, etc to no avail because they are not recursive and will not go into each key and array.
And all the answers I've seen in StackOverflow are to get the value by name inside a one-dimensional array which doesn't help.
How can I get the Infobox data from this?
Is this what you're looking for?
pp data
=> {:query=> {:pages=>
{:"743958"=>
{:pageid=>743958,
:ns=>0,
:title=>"Asterix the Gaul",
:revisions=>
[{:contentformat=>"text/x-wiki",
:contentmodel=>"wikitext",
:*=>"{{Cleanup..."}]}}}}
# just return data from the first revisionb
data[:query][:pages].map{|page_id,page_hash| page_hash[:revisions].first[:"*"]}
=> ["{{Cleanup..."]
# get data from all revisions
data[:query][:pages].map{|page_id,page_hash| page_hash[:revisions].map{|revision| revision[:"*"] }}.flatten
=> ["{{Cleanup..."]

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/, "}")
)

How to construct the 2d structure in a dynamic fashion

I iterate through all cars and its supported attributes (many attributes per car) to create a structure like this, how do I do this in a dynamic fashion.
cars = {
"honda" => {'color' => 'blue', 'type' => 'sedan'}.
"nissan" => {'color' => 'yellow', 'type' => 'sports'}.
...
}
cars.each do |car|
car_attrs = ...
car_attrs.each do |attr|
??? How to construct the above structure
end
end
Your question is not very clear... But i guess this is what you want:
cars = {}
options = {}
options['color'] = 'blue'
...
cars['honda'] = options
Is that what you were looking for?
It sounds like you may be asking for a way to create a 2-dimensional hash without having to explicitly create each child hash. One way to accomplish that is by specifying the default object created for a hash key.
# When we create the cars hash, we tell it to create a new Hash
# for undefined keys
cars = Hash.new { |hash, key| hash[key] = Hash.new }
# We can then assign values two-levels deep as follows
cars["honda"]["color"] = "blue"
cars["honda"]["type"] = "sedan"
cars["nissan"]["color"] = "yellow"
cars["nissan"]["type"] = "sports"
# But be careful not to check for nil using the [] operator
# because a default hash is now created when using it
puts "Found a Toyota" if cars["toyota"]
# The correct way to check would be
puts "Really found a Toyota" if cars.has_key? "toyota"
Many client libraries assume that the [] operator returns a nil default, so make sure other code doesn't depend on that behavior before using this solution. Good luck!
Assuming you are using something similar to ActiveRecord (but easy to modify if you are not):
cars_info = Hash[cars.map { |car| [car.name, car.attributes] }

Resources