Cast a returned array to appropriate values - ruby

I've got the following code snippet which I'm trying to return to Shoes as an array of integers and strings
Shoes.setup do
gem 'snmp'
end
class SNMPManager
require 'snmp'
include SNMP
attr_accessor :hostname, :framerate
def initialize(params = {})
#hostname = params.fetch(:hostname, 'localhost')
#framerate = params.fetch(:framerate, 25)
end
def getValues
Manager.open(:host => #hostname, :mib_modules => ["SNMPv2-MIB"]) do |manager|
poolArray = []
ifTable = ObjectId.new("1.3.6.1.4.1.15248.2.5.1.3.1")
manager.walk(ifTable) do |row|
poolArray.push(row.inspect)
end
groups = poolArray.group_by{ |s| s.split(',').first[/\d+$/] }
#values = groups.map{ |key, ary| ary.map{ |s| s[/value=(\S+)/, 1] } }
end
end
def to_s
return #values
end
end
Shoes.app do
##hostname = edit_line
#man =SNMPManager.new(:host => #hostname)
man = SNMPManager.new
man.getValues
puts 'SNMP Space Monitor'
man.to_s.each {|v|
#note = para "#{v[1]} \t(Pool: #{v[0]}) \tCapacity: #{v[3].to_i} \tFree Protons: #{v[2].to_i} \tPercent Free: %#{(v[3].to_f/v[2].to_f*100).round(2)}"
}
end
For some reason Shoes is displaying it like:
But if I run the ruby script normally, and output it with puts, it displays correctly, and calculates the response correctly.
I know I'm missing something obvious, but can't see what it is.
I'm trying to cast the array to another array, but that seems to do bugger all.

1)
#values = Array(#values)
That is a bit odd. All the Array constructor does is create a copy of #values. The only reason you would do that is if #values contains a reference to some array that is referenced by another variable, and you don't want that other variable to be able to change #values. But then why didn't you make a copy when you first assigned the array to #values?
2) Your to_s method does not return a string.

This is a great way to use Shoes.
It looks like Shoes is giving you paras containing array_of_integers_and_strings.to_s.
Complete code would be super helpful.

Resolved the issue by switching from 'red shoes' to 'green shoes'
No other changes required, but here's my current code (which successfully times out when run as a console app. but not as a shoes app - go figure)
require 'green_shoes'
gem 'snmp'
class SNMPManager
require 'snmp'
include SNMP
attr_accessor :hostname, :framerate
def initialize(params = {})
#hostname = params.fetch(:hostname, 'localhost')
#framerate = params.fetch(:framerate, 25)
end
def getValues
Manager.open(:host => #hostname) do |manager|
#poolArray = []
poolsTable = ObjectId.new("1.3.6.1.4.1.15248.2.5.1.3.1")
manager.walk(poolsTable) do |row|
#poolArray.push(row.inspect)
end
if #poolArray.empty?
puts "Manager Down"
#poolArray = [
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8650, value=8650 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.1.8651, value=8651 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8650, value=QNewsAK (OCTET STRING)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.2.8651, value=QSuite4AK (OCTET STRING)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8650, value=46835255 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.3.8651, value=11041721 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8650, value=8442357 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.4.8651, value=5717570 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8650, value=0 (INTEGER)]',
'[name=1.3.6.1.4.1.15248.2.5.1.3.1.5.8651, value=0 (INTEGER)]',
]
else puts "Manager Up"
end
groups = #poolArray.group_by{ |s| s.split(',').first[/\d+$/] }
#values = groups.map{ |key, ary| ary.map{ |s| s[/value=(\S+)/, 1] } }
end
end
def to_s
return #values
end
protected
end
Shoes.app(:title=>'SNMP Space Monitor') do
man = SNMPManager.new
stack do
every(300) {
man.getValues
man.to_s
man.to_s.each {|v|
para "#{v[1]} \t(Pool: #{v[0]}) \tCapacity: #{v[2].to_i} \tFree Protons: #{v[3].to_i} \tPercent Free: #{(v[3].to_f/v[2].to_f*100).round(2)}%"
}
}
end
end

Related

How to call hash values outside class from defined hash map inside class methods?

Read a csv format file and construct a new class with the name of the file dynamically. So if the csv is persons.csv, the ruby class should be person, if it's places.csv, the ruby class should be places
Also create methods for reading and displaying each value in "csv" file and values in first row of csv file will act as name of the function.
Construct an array of objects and associate each object with the row of a csv file. For example the content of the csv file could be
name,age,city
abd,45,TUY
kjh,65,HJK
Previous code :
require 'csv'
class Feed
def initialize(source_name, column_names = [])
if column_names.empty?
column_names = CSV.open(source_name, 'r', &:first)
end
columns = column_names.reduce({}) { |columns, col_name| columns[col_name] = []; columns }
define_singleton_method(:columns) { column_names }
column_names.each do |col_name|
define_singleton_method(col_name.to_sym) { columns[col_name] }
end
CSV.foreach(source_name, headers: true) do |row|
column_names.each do |col_name|
columns[col_name] << row[col_name]
end
end
end
end
feed = Feed.new('input.csv')
puts feed.columns #["name", "age", "city"]
puts feed.name # ["abd", "kjh"]
puts feed.age # ["45", "65"]
puts feed.city # ["TUY", "HJK"]
I am trying to refine this solution using class methods and split code into smaller methods. Calling values outside the class using key names but facing errors like "undefined method `age' for Feed:Class". Is that a way I can access values outside the class ?
My solution looks like -
require 'csv'
class Feed
attr_accessor :column_names
def self.col_name(source_name, column_names = [])
if column_names.empty?
#column_names = CSV.open(source_name, :headers => true)
end
columns = #column_names.reduce({}) { |columns, col_name| columns[col_name] = []; columns }
end
def self.get_rows(source_name)
col_name(source_name, column_names = [])
define_singleton_method(:columns) { column_names }
column_names.each do |col_name|
define_singleton_method(col_name.to_sym) { columns[col_name] }
end
CSV.foreach(source_name, headers: true) do |row|
#column_names.each do |col_name|
columns[col_name] << row[col_name]
end
end
end
end
obj = Feed.new
Feed.get_rows('Input.csv')
puts obj.class.columns
puts obj.class.name
puts obj.class.age
puts obj.class.city
Expected Result -
input = Input.new
p input.name # ["abd", "kjh"]
p input.age # ["45", "65"]
input.name ='XYZ' # Value must be appended to array
input.age = 25
p input.name # ["abd", "kjh", "XYZ"]
p input.age # ["45", "65", "25"]
Let's create the CSV file.
str =<<END
name,age,city
abd,45,TUY
kjh,65,HJK
END
FName = 'temp/persons.csv'
File.write(FName, str)
#=> 36
Now let's create a class:
klass = Class.new
#=> #<Class:0x000057d0519de8a0>
and name it:
class_name = File.basename(FName, ".csv").capitalize
#=> "Persons"
Object.const_set(class_name, klass)
#=> Persons
Persons.class
#=> Class
See File::basename, String#capitalize and Module#const_set.
Next read the CSV file with headers into a CSV::Table object:
require 'csv'
csv = CSV.read(FName, headers: true)
#=> #<CSV::Table mode:col_or_row row_count:3>
csv.class
#=> CSV::Table
See CSV#read. We may now create the methods name, age and city.
csv.headers.each { |header| klass.define_method(header) { csv[header] } }
See CSV#headers, Module::define_method and CSV::Row#[].
We can now confirm they work as intended:
k = klass.new
k.name
#=> ["abd", "kjh"]
k.age
#=> ["45", "65"]
k.city
#=> ["TUY", "HJK"]
or
p = Persons.new
#=> #<Persons:0x0000598dc6b01640>
p.name
#=> ["abd", "kjh"]
and so on.

Make an array in Ruby

I am very beginner in Ruby and probably the question is too easy but well, I've already spent some time on it and couldn't find a solution.
My Ruby script takes a number (ex 10) and a name (ex Vincent). What I want is to make an array looking like
Vincent0
Vincent1..
Vincent9
I can't figure a way to make it..
def arrayfy(string, number)
arr = []
0.upto(number-1) do |i|
arr << "#{string}#{i}"
end
return arr
end
Update: To add these as variables to the class
class Foo
def arrayfy(string, number)
0.upto(number-1) do |i|
var_string = "##{string}#{i}"
var_symbol = var_string.to_sym
self.instance_variable_set(var_symbol, "")
end
end
end
Array.new(10) {|i| "Vincent#{i}"}
gives you
["Vincent0", "Vincent1", "Vincent2", "Vincent3", "Vincent4", "Vincent5",
"Vincent6", "Vincent7", "Vincent8", "Vincent9"]
The documentation for Array is available at http://www.ruby-doc.org/core/classes/Array.html (googling for Array RDoc will give you the URL).
The bit in the braces ({|i| "Vincent#{i}"}) is called a block. You'll definitely want to learn about them.
Using Array.new with a block (docs):
def create_array(count, name)
Array.new(10) { |i| "#{name}#{i} }
end
Using Enumerable#reduce (docs):
def create_array(count, name)
(0...count).reduce([]) { |m,i| m << "#{name}#{i}" }
end
Or using Enumerable#each_with_object (docs):
def create_array(count, name)
(0...count).each_with_object([]) { |i,a| a << "#{name}#{i}" }
end
Using it:
# Using the array (assigning to variables)
array = create_array(10, 'Vincent') # => ['Vincent0', 'Vincent1', 'Vincent2' ...]
name = array[1] # => 'Vincent1'
Just for the record, a solution in a more functional style:
>> def arrayify(str, n)
.. ([str] * n).zip(0...n).map(&:join)
.. end
#=> nil
>> arrayify('Vincent', 10)
#=> ["Vincent0", "Vincent1", "Vincent2", "Vincent3", "Vincent4", "Vincent5", "Vincent6", "Vincent7", "Vincent8", "Vincent9"]
def array_maker(number, string)
result = []
for i in 0..number do
result << "#{string}#{i}"
end
result
end

Return a single value from a nested each block, trying to use 'return'

def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
return item[:type]
end
}
}
end
result = get_type
puts "result: #{result}"
Expected output:
Checking foo.* against foo.txt
Found match: A
result: A
However, all I see is:
Checking foo.* against foo.txt
Found match: A
My current work around is this:
def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = []
x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
result << item[:type]
end
}
}
result[0] unless result.empty?
end
Why doesn't the first approach work? or maybe it is 'working', I just don't understand why I'm not getting what I'd expect.
May I suggest a refactor? your code looks kind of clunky because you are using each loops (imperative) when you in fact need a map+first (functional). As Ruby enumerables are not lazy this would be inefficient, so people usually build the abstraction Enumerable#map_detect (or find_yield, or find_first, or map_first):
def get_type_using_map_detect(name)
xs = [{:type => 'A', :patterns => ['foo.*']}, {:type => 'B', :patterns => ['bar.*']}]
xs.map_detect do |item|
item[:patterns].map_detect do |regex|
item[:type] if name.match(regex)
end
end
end
This is a possible implementation of the method:
module Enumerable
# Like Enumerable#map but return only the first non-nil value
def map_detect
self.each do |item|
if result = (yield item)
return result
end
end
nil
end
end
Works fine for me. Are you actually invoking it with
result = get_type puts "result: #{result}"
? Because that shouldn't work at all, though I'm assuming there's a linefeed that got eaten when you posted this.

Most Efficient way to Recursively Flatten Nested Array to Dot-separated string in Ruby?

I want to convert something like this:
class NestedItem
attr_accessor :key, :children
def initialize(key, &block)
self.key = key
self.children = []
self.instance_eval(&block) if block_given?
end
def keys
[key] + children.keys
end
end
root = NestedItem.new("root") do
children << NestedItem.new("parent_a") do
children << NestedItem.new("child_a")
children << NestedItem.new("child_c")
end
children << NestedItem.new("parent_b") do
children << NestedItem.new("child_y")
children << NestedItem.new("child_z")
end
end
require 'pp'
pp root
#=>
# #<NestedItem:0x1298a0
# #children=
# [#<NestedItem:0x129814
# #children=
# [#<NestedItem:0x129788 #children=[], #key="child_a">,
# #<NestedItem:0x12974c #children=[], #key="child_c">],
# #key="parent_a">,
# #<NestedItem:0x129738
# #children=
# [#<NestedItem:0x129698 #children=[], #key="child_y">,
# #<NestedItem:0x12965c #children=[], #key="child_z">],
# #key="parent_b">],
# #key="root">
Into this:
root.keys #=>
[
"root",
"root.parent_a",
"root.parent_a.child_a",
"root.parent_a.child_c",
"root.parent_b",
"root.parent_b.child_y",
"root.parent_b.child_z",
]
...using a recursive method.
What's the simplest way to go about this?
Update
I did this:
def keys
[key] + children.map(&:keys).flatten.map do |node|
"#{key}.#{node}"
end
end
Anything better?
Would Array.flatten work for you?
self.children.flatten should return the flattened results.
Yes, .flatten will produce what I think you really want.
But if you want exactly the string output you typed, this will do it:
def keys x
here = key
here = x + '.' + here if x
[ here ] + children.inject([]) { |m,o| m += o.keys here }
end
pp root.keys nil
Or, alternatively, replace the last line in #keys with:
([ here ] + children.map { |o| o.keys here }).flatten

Recursively merge multidimensional arrays, hashes and symbols

I need a chunk of Ruby code to combine an array of contents like such:
[{:dim_location=>[{:dim_city=>:dim_state}]},
:dim_marital_status,
{:dim_location=>[:dim_zip, :dim_business]}]
into:
[{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]},
:dim_marital_status]
It needs to support an arbitrary level of depth, though the depth will rarely be beyond 8 levels deep.
Revised after comment:
source = [{:dim_location=>[{:dim_city=>:dim_state}]}, :dim_marital_status, {:dim_location=>[:dim_zip, :dim_business]}]
expected = [{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]}, :dim_marital_status]
source2 = [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]
def merge_dim_locations(array)
return array unless array.is_a?(Array)
values = array.dup
dim_locations = values.select {|x| x.is_a?(Hash) && x.has_key?(:dim_location)}
old_index = values.index(dim_locations[0]) unless dim_locations.empty?
merged = dim_locations.inject({}) do |memo, obj|
values.delete(obj)
x = merge_dim_locations(obj[:dim_location])
if x.is_a?(Array)
memo[:dim_location] = (memo[:dim_location] || []) + x
else
memo[:dim_location] ||= []
memo[:dim_location] << x
end
memo
end
unless merged.empty?
values.insert(old_index, merged)
end
values
end
puts "source1:"
puts source.inspect
puts "result1:"
puts merge_dim_locations(source).inspect
puts "expected1:"
puts expected.inspect
puts "\nsource2:"
puts source2.inspect
puts "result2:"
puts merge_dim_locations(source2).inspect
I don't think there's enough detail in your question to give you a complete answer, but this might get you started:
class Hash
def recursive_merge!(other)
other.keys.each do |k|
if self[k].is_a?(Array) && other[k].is_a?(Array)
self[k] += other[k]
elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
self[k].recursive_merge!(other[k])
else
self[k] = other[k]
end
end
self
end
end

Resources