Add to existing hash inside of a class ruby - ruby

I am trying to create a hash that has a data structure like so:
hash = {
:a => { :a1 => "x", :a2: => "x", :a3 => "x" },
:b => { :b1 => "x", :b2: => "x", :b3 => "x" },
}
Inside of a class function. I am rather new to OO, so maybe I'm not understanding the variable scoping correctly.
Here is my code:
class Foo
# Control class args
attr_accessor :site, :dir
# Initiate our class variables
def initialize(site,dir)
#site = site
#dir = dir
##records = {}
#records = Hash.new { |h, k| h[k] = Hash.new }
end
def grab_from_it
line = %x[tail -1 #{#dir}/#{#site}/log].split(" ")
time = line[0, 5].join(" ")
rc = line[6]
host = line[8]
ip = line[10]
file = line[12]
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
end
end
Main body:
foo = Foo.new(site,dir)
foo.grab_from_it
pp foo
sleep(10)
foo.grab_from_it
pp foo
It works and successfully creates a hash with my desired structure, but when I run again, it overwrites the existing hash. I want it to keep adding to it, so I can create a "running tab".

Replace the following line
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
with
#records["#{file}"] = { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" }
Every time you call #records = {} the instance variable points to a new hash. Thus the initialization code in initialize has no effect. Rather than replace the initialized hash with a new one, you should add new entry to the existing hash, using the []= instance method of Hash.
BTW, you can use variable to refer to the string rather than creating a new one using string interpolation "#{variable}".
#records[file] = { :time => time, :rc => rc, :host => host, :ip => ip }
If you want the UPDATE behavior for both the first and the second layers of the hash, you can take a look at the Hash#update method.
#records[file].update({ :time => time, :rc => rc, :host => host, :ip => ip })

Related

Ruby comparing values in hashes and returning the same structure

I have 2 hashes like so:
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
...
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
...
:different_x => "some_x_value"
}
}
I find the common values in the :values key of both the hashes like this :
same_values = hash[:values].values & compared_hash[:values].values
And once I get it, I want to return a new hash similar to 'stored_hash', but with the :values of it containing the same_values I found before.
For example, if both hashes have "some_value", "some_other_value", "a_new_value", my new hash should look like :
new_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value"
}
}
This should work for you :
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_a_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "some_other_value",
:different_3 => "a_new_value",
:different_x => "some_b_value"
}
}
common_values = compare_hash[:values].values & stored_hash[:values].values
new_hash = stored_hash.dup
new_hash[:values] = {}
common_values.each_with_index do |value, index |
new_hash[:values]["value_#{index+1}".to_sym] = value
end
new_hash
# => new_hash = {
# :name => "hash_1",
# :version => "1.0",
# :values => {
# :value_1 => "some_value",
# :value_2 => "some_other_value",
# :value_3 => "a_new_value"
# }
# }
This is one way you could obtain your desired result.
Code
require 'set'
def make_new_hash(stored_hash, compare_hash)
new_hash = stored_hash.dup
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
values_hash = new_hash[:values]
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
new_hash
end
Example
stored_hash = {
:name => "hash_1",
:version => "1.0",
:values => {
:value_1 => "some_value",
:value_2 => "some_other_value",
:value_3 => "a_new_value",
:value_x => "some_x_value"
}
}
compare_hash = {
:name => "hash_2",
:version => "2.0",
:values => {
:different_1 => "some_value",
:different_2 => "a_new_value",
:different_3 => "some_strange_value",
:different_x => "some_x_value"
}
}
Note that I've made a small change in stored_hash from that given in the question.
make_new_hash(stored_hash, compare_hash)
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Explanation
Create a copy of stored_hash:
new_hash = stored_hash.dup
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value",
# :value_2=>"some_other_value",
# :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
We only want to keep a key k of new_hash[:values] if new_hash[:values][k] is one of the values in both the hash stored_hash[:values] and the hash compare_hash[:values], so we obtain those values:
compare_values =
(stored_hash[:values].values & compare_hash[:values].values).to_set
# => #<Set: {"some_value", "a_new_value", "some_x_value"}>
I chose to save them in a set both to speed lookup and to obtain unique values. They could alternatively be saved to array like so:
(stored_hash[:values].values & compare_hash[:values].values).uniq
#=> ["some_value", "a_new_value", "some_x_value"]
The code that follows is the same if an array is used rather than a set.
To simpify, let's create a variable:
values_hash = new_hash[:values]
#=> {:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}
Next determine the keys of New_hash[:values] we wish to keep:
keys_to_keep = values_hash.keys.select { |k|
compare_values.include?(values_hash[k])}
#=> [:value_1, :value_3, :value_x]
All keys other than :value_2 are kept. That key is not kept because comparative_hash[:values] does not have a value "some_other_value".
We can now construct the updated value of new_hash[:values]:
new_hash[:values] =
Hash[keys_to_keep.zip(values_hash.values_at(*keys_to_keep))]
#=> {:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}
Lastly, we return new_hash:
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
Let's confirm that stored_hash will not be changed when a value of new_hash[:values] is changed:
new_hash[:values][:value_1] = 'cat'
new_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"cat", :value_3=>"a_new_value",
# :value_x=>"some_x_value"}}
stored_hash
#=> {:name=>"hash_1", :version=>"1.0",
# :values=>{:value_1=>"some_value", :value_2=>"some_other_value",
# :value_3=>"a_new_value", :value_x=>"some_x_value"}}

Complex map by objects property in ruby

I'm trying to create different kind of structure from the array I currently have in my code.
I've got an array of objects (active directory objects) so lets say i got a from db :
a = [o1,o2,o3,o4,o5]
My object has property source_id and name which are the relevant properties.
I want to create structure like this (I want hash) from the data I have in my array :
objects = Hash.new { |hash, key| hash[key] = [] }
And this would be one example of how to put the data inside new structure:
a.each do |ob|
objects[ob.source_id] << {
:new => '',
:name => {:unformated => ob.name, :formatted => ob.format(:name)}
}
end
I'm trying to replicate the same structure and it's not working out in my case :
a.group_by(&:source_id).map do |k,v|
{
k=> {
{
:new => '',
:name => {:unformated => v.name, :formatted => ob.format(:name)}
}
}
}
end.reduce(:merge)
This is the error I get :
! #<NoMethodError: undefined method `name' for #<Array:0xae542b4>>
With group_by your values in the a.group_by(&:source_id).map are going to be arrays of elements sharing the same source_id and not individual elements.
The following code may do what you wish:
a.group_by(&:source_id).map do |k,v|
{
k => v.map do |e|
{
:new => '',
:name => {:unformated => e.name, :formatted => e.format(:name)}
}
end
}
end.reduce(:merge)
Heres a one liner to do the same
new_hash = a.each_with_object({}) { |o, hash| hash[o.source_id] = {:new => '', :unformatted => o.name, :formatted => o.format(:name)} }
This does require ruby 1.9.3 though

Accessing array within a hash within a hash (adding new values)

Thank you for the help in advance!
I'd like to add or push a value into my array which is currently within a person hash and task hash.
Look below at the add task section task.merge!
task = {
"person1" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person2" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person3" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
}
puts task["person1"][:high][0]
# create task
#create new person hash within task hash
#already have :high, :mid, & :low hash
# add task
#add a task to the array within either high,mid, or low
task.merge!("person1" => {:high => "#{new_task}")
# remove task
#delete method
task.merge! "person1" => {:high => "#{new_task}"}
You simply had an error there. I removed the parenthesis to make it maybe a little more obvious.

Ruby - multidimensional hash

How do I make this multidimensional? Each search result entry has multiple attributes :attributes => ['sAMAccountName','givenName','SN','mail']. and there can be many entries in the result. This code is good for creating only one entry with multiple attributes.
def self.Find(attribute, loginID)
conn = Net::LDAP.new :host => SERVER,
:port => PORT,
:base => BASE,
:auth => {
:username => 'admin',
:password => 'admin',
:method => :simple
}
if conn.bind
result = HashWithIndifferentAccess.new
conn.search( :base => LDAPBASE,
:filter => Net::LDAP::Filter.eq( attribute, loginID+"*" ),
:attributes => ['sAMAccountName','givenName','SN','mail'],
:return_result => true
).each do |entries|
entries.each do |attribute, values|
values.each do |value|
result[attribute] = value
end
end
end
result
end
end
The actual response I get from ldap is like this -
puts result.to_s
{
"dn"=>"CN=somename\\, somefirstname,OU=Users,DC=site,DC=com",
"sn"=>"somename",
"givenname"=>"somefirstname",
"samaccountname"=>"someuserid",
"mail"=>"someone#somthing.com"
}
Since the search is has * wildcard to find all matching entries. Ldap will return multiple entries in the format above. similar to this -
{
"dn"=>"CN=somename\\, somefirstname1,OU=Users,DC=site,DC=com",
"sn"=>"somename1",
"givenname"=>"somefirstname1",
"samaccountname"=>"someuserid1",
"mail"=>"someone1#somthing.com"
},
{
"dn"=>"CN=somename\\, somefirstname2,OU=Users,DC=site,DC=com",
"sn"=>"somename2",
"givenname"=>"somefirstname2",
"samaccountname"=>"someuserid2",
"mail"=>"someone2#somthing.com"
},
{
"dn"=>"CN=somename\\, somefirstname3,OU=Users,DC=site,DC=com",
"sn"=>"somename3",
"givenname"=>"somefirstname3",
"samaccountname"=>"someuserid3",
"mail"=>"someone3#somthing.com"
},
A[a=>1,b=>11,c=>111]
B[a=>2,b=>22,c=>222]
C[a=>3,b=>33,c=>333]
D[a=>4,b=>44,c=>444]
I'm not sure that I understood the question.
def self.Find(attribute, loginID)
conn = Net::LDAP.new :host => SERVER,
:port => PORT,
:base => BASE,
:auth => {
:username => 'admin',
:password => 'admin',
:method => :simple
}
if conn.bind
conn.search( :base => LDAPBASE,
:filter => Net::LDAP::Filter.eq( attribute, loginID+"*" ),
:attributes => ['sAMAccountName','givenName','SN','mail'],
:return_result => true
).reduce(Array.new) do |acc, el|
#
# Any modification of the entry must be here
#
acc + [el]
end
end
end
end
I think Array.map is good choice too
My assumption:
conn.search return Array of Hash
the form of result you want:
[
{"uid":"123","displayName":"User 123","mail":"123#example.com"},
{"uid":"456","displayName":"User 456","mail":"456#example.com"},
{"uid":"789","displayName":"User 789","mail":"789#example.com"}
]

Creating Objects at Run-time in Ruby

PHP
<?php
$dynamicProperties = array("name" => "bob", "phone" => "555-1212");
$myObject = new stdClass();
foreach($dynamicProperties as $key => $value) {
$myObject->$key = $value;
}
echo $myObject->name . "<br />" . $myObject->phone;
?>
How do I do this in ruby?
If you want to make a "dynamic" formal class, use Struct:
>> Person = Struct.new(:name, :phone)
=> Person
>> bob = Person.new("bob", "555-1212")
=> #<struct Person name="bob", phone="555-1212">
>> bob.name
=> "bob"
>> bob.phone
=> "555-1212"
To make an object completely on-the-fly from a hash, use OpenStruct:
>> require 'ostruct'
=> true
>> bob = OpenStruct.new({ :name => "bob", :phone => "555-1212" })
=> #<OpenStruct phone="555-1212", name="bob">
>> bob.name
=> "bob"
>> bob.phone
=> "555-1212"
Use OpenStruct:
require 'ostruct'
data = { :name => "bob", :phone => "555-1212" }
my_object = OpenStruct.new(data)
my_object.name #=> "bob"
my_object.phone #=> "555-1212"
One of many ways to do this is to use class_eval, define_method and so on to construct a class dynamically:
dynamic_properties = {
'name' => 'bob',
'phone' => '555-1212'
}
class_instance = Object.const_set('MyClass', Class.new)
class_instance.class_eval do
define_method(:initialize) do
dynamic_properties.each do |key, value|
instance_variable_set("##{key}", value);
end
end
dynamic_properties.each do |key, value|
attr_accessor key
end
end
You can then consume this class later on as follows:
>> my_object = MyClass.new
>> puts my_object.name
=> 'bob'
>> puts my_object.phone
=> '555-1212'
But it wouldn't be Ruby if there was only one way to do it!

Resources