Undefined method for array - ruby

So I'm receiving the error message "undefined method 'ip_histogram' for # (NoMethodError)" with the following code
class CommonLog
def initialize(logfile)
#logfile = logfile
end
def readfile
#readfile = File.readlines(#logfile).map { |line|
line.split()
}
#readfile = #readfile.to_s.split(" ")
end
def ip_histogram
#ip_count = 0
#readfile.each_index { |index|
if (#readfile[index] =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ )
puts #readfile[index]
puts #ip_count += 1
end
}
end
end
my_file = CommonLog.new("test_log")
cleaned_file = my_file.readfile
puts cleaned_file.ip_histogram
I'm trying to read the array in and check it with regex. It works if I take the code out of the method and just throw it into the main program, but I'd like to know why it doesn't work as a method.

You're assigning the results of CommonLog#readfile to a new variable cleaned_file. You didn't supply any input but I'm going to assume #readfile is an array at this point.
That means your code is going to assume there's an instance method ip_histogram on the Array class when in fact there is not.
You'll simply want to run these in sequence to get the result you're after:
clog = CommonLog.new("test_log")
clog.readfile
clog.ip_histogram
The primary difference here is that we're not using the return value of readfile as it only needs to set the variable in the method for ip_histogram to work.

The return value of my_file.readfile is an array. Unless you defined ip_histogram in Array class, you cannot call it on cleaned_file.
To define ip_histogram on Array (with different definition than in your code):
class Array
def ip_histogram
...
end
end

Related

How would one return a value from a private method to a public method

I'm creating a program in ruby which I'm new to the language and was wondering how I would return the data from a private method to a public method so when the method is called upon the data is shown
I've looked around online but with being new to the language I'm finding it hard to transfer what I'm seeing to what I need in my code
def display
# return calcutalateFrequencies data here
end
private
def calculateFrequencies
#text = text.downcase.chars.each do |c|
next if c =~ /\s/
#letters[c] += 1
end
end
You can just call the private method in the public method.
def display
calculateFrequencies
end
A private method is just a method that can ONLY be called by other methods in the object, so the above is the only way calculateFrequencies would work (excepting if you were using special methods like send)
It could be done in many different ways. Since you are setting instance variables, you might use them or return the result as is.
[I have slightly modified the code to look more rubyish.]
def display
puts calculateFrequencies("foo bar")
# or puts #letters
end
private
def calculateFrequencies(text)
#text = text
#letters =
text.downcase.each_char.with_object(Hash.new(0)) do |c, letters|
next if c == " "
letters[c] += 1
end
end
You've changed the question slightly, but things you need to consider:
methods return the last executed statement. If you do an each as
the last executed statement (with or without a block) the each
enumerable is returned.
If the method modifies instance variables
(as yours do) you may not need to look at what is returned by the
method, just display the instance variables.
An example of making this work...
class FrequencyCalculator
def initialize(text)
#text = text
calculateFrequencies
end
def display
#letters
end
private
def calculateFrequencies
#letters = Hash.new(0)
#text.downcase.chars.each do |c|
next unless c =~ /\s/ # change from "if" to "unless" otherwise you're skipping letters
#letters[c] += 1
end
end
end
And then you could do
frequency_calculator = FrequencyCalculator.new('Hello World')
frequency_calculator.display
Or more compactly
FrequencyCalculator.new('Hello World').display

Creating a Hash with a method

I am having trouble creating a method to establish a new hash. I know that it is definitely easier just to declare the hash, however I need to create a method. Here is what I have so far and it keeps generating an error message.
def create_new(hash_name)
hash_name = Hash.new
end
This should create and empty hash^
def add_item(hash_name, item_name, item_quantity)
hash_name[:item_name.to_sym] = item_quantity
end
I keep getting an error message on the above code^ I am trying to update this hash and add a new key value pair with a method
p create_new("grocery_list")
This creates a new empty hash^ however when I call it with the below code is says the hash is undefined
add_item(grocery_list, "pizza", "1")
p grocery_list
You could also turn it into a class if you fancy.
class MyHash
attr_reader :hash
def initialize
#hash = Hash.new
end
def [](key)
self.hash[key]
end
def []=(key, value)
self.hash[key.to_sym] = value
end
end
grocery_list = MyHash.new
grocery_list['pizza'] = 1
> grocery_list.hash
=> {:pizza=>1}
in your create_new method, you define a hash_name local variable. This variable does not exist anywhere but the body of your method. That's what seems to confuse you.
You could express better your intent with :
def create_new
Hash.new
end
def add_item(hash, key, value)
hash[key.to_sym] = value
end
In order to get to what you are trying to do, you will have to store the result of your method in some kind of variable in order to use it :
grocery_list = create_new # grocery_list is now a local variable
add_item(grocery_list, 'pizza', 1)

Cannot push into ruby array

I am writing a new accessor and it has its own array variable to keep information but when I try to call class_eval in its method, the push method on that variable doesn't work.
Its a method written in Class and the class_eval line reads as follows:
class_eval "def #{attr_name}=(value); #{attr_name} = value; #{information}.push value; end; def #{attr_name}_history; #{information}; end"
so the push in this line doesn't work.
You have to make sure that whatever string is stored by the local variable information when you call the class_eval is actually a method on the class that you're adding this function to.
The following works, because I make sure the class has a history method.
class A
def history
#history ||= []
end
end
attr_name = "foo"
information = "history"
A.class_eval "def #{attr_name}=(value); #{attr_name} = value; #{information}.push value; end; def #{attr_name}_history; #{information}; end"
a = A.new
a.foo = "bar"
a.foo = "baz"
a.foo_history
It won't work because "#{information}" will return the string value of information, not the actual Array object itself. This is because it is substituted for a string when it is built.
You have to change it to a variable that is part of both the scope of the object you're working in, and the class you're doing things to. For example, try changing it to something like:
Foo::Information = [...]
class_eval "
class_eval "
def #{attr_name}=(value)
#{attr_name} = value
Foo::Information.push value
end
def #{attr_name}_history
Foo::Information
end"
"
This is because information variable should be initialized as an array, then you can call push method on it. Something like this:
def #{attr_name}=(value);
#{attr_name} = value;
(#{information}||=[]).push value;
end;
def #{attr_name}_history;
#{information};
end
BTW, your formating is ugly, so you have to think twice next time.

Ruby: what is wrong with this change_keys functions?

class Klass
attr_accessor :keys
def change_keys(opt)
if opt == 1
keys = [keys[0], keys[keys.length - 1]]
else
tmp = keys[0]
keys[0] = keys[keys.length-1]
keys[keys.length-1] = tmp
end
keys
end
end
klass = Klass.new
klass.keys = [1,2,3,4,5]
# puts klass.change_keys(1)
# puts klass.change_keys(2)
This is not working at all, the error says: undefined method '[]' method for nil:NilClass
Ruby interprets keys[0] and the others in line 5 as local variables because it has seen a keys = ... . There is an ambiguity in Ruby grammar when it comes to differentiate local variables from method calls without arguments that gets disambiguated by that heuristic. that is, if the parser sees an assignment to that identifier then is a local variable if not is a method call.
You can solve this by referring to self.keys instead. to make clear that you want to use the accessor method.
class Klass
attr_accessor :keys
def change_keys(opt)
if opt == 1
self.keys = [keys[0], keys[keys.length - 1]]
else
tmp = keys[0]
keys[0] = keys[keys.length-1]
keys[keys.length-1] = tmp
end
keys
end
end
klass = Klass.new
klass.keys = [1,2,3,4,5]
puts klass.change_keys(1)
puts klass.change_keys(2)
It looks like you're expecting keys, within your change_keys method, to refer to an instance variable (the same one as you set explicitly by writing klass.keys = [1,2,3,4,5]), but it doesn't. You want #keys instead.
(There are programming languages, such as C++ and Smalltalk and Java, in which unadorned variable names are automatically taken to refer to instance variables. Ruby isn't one of them.)
You did not define an instance variable named keys to reference it. I would add the initialization and use the #keys instance variable:
class Klass
#attr_accessor :keys
def initialize(keys)
#keys = keys
end
def change_keys(opt)
if opt == 1
#keys = [#keys[0], #keys[#keys.length - 1]]
else
tmp = #keys[0]
#keys[0] = #keys[#keys.length-1]
#keys[#keys.length-1] = tmp
end
#keys
end
end
klass = Klass.new([1,2,3,4,5])
# puts keys.change_keys(1)
# puts keys.change_keys(2)
Try it out and let me know.
Attributes are instance variables, so you should be referring to keys as #keys everywhere within your method.

How does this ruby custom accessor work

So the method below in class_eval dynamically creates accessors for attributes defined at runtime. It can be used, for example, to create configuration objects with attributes read from a config file (and unknown until runtime). I understanding all of it except for the else branch. If I am correct the else branch returns the attribute value (val[0]) if there is one value passed in *val. However the way its written I would expect it to return an array (val) if there is more then one value passed in *var. In particular, if I have something like the following:
value = 5
then from reading the code I would expect #value to be [=,5]. However #value returns 5 and not the array [=,5]. How is this possible?
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}(*val)
if val.empty?
##{sym}
else
##{sym} = val.size == 1 ? val[0] : val
end
end
}
end
end
end
An equals sign is not an argument for the method, it's a part of the method name. Actually you can call an assignment like this:
value=(5)
So only the integer 5 is an argument for the function.
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}
##{sym}
end
def #{sym}=(val)
##{sym} = val
end
}
end
end
end

Resources