Fikayo Olatunji Fikayo Olatunji - 10 days ago 6
Ruby Question

I an getting an "Undefined method 'new' for.... (A number that changes each time)"

I made a simple program with a single method and I'm trying to test it, but I keep getting this weird error, and I have no idea why it keeps happening.
Here's my code for the only method I wrote:

def make_database(lines)
i = 0
foods = hash.new()
while i < lines.length do
lines[i] = lines[i].chomp()
words = lines[i].split(',')
if(words[1].casecmp("b") == 0)
foods[words[0]] = words[3]
end
end
return foods
end


And then here's what I have for calling the method (Inside the same program).

if __FILE__ == $PROGRAM_NAME
lines = []
$stdin.each { |line| lines << line}
foods = make_database(lines).new
puts foods
end


I am painfully confused, especially since it gives me a different random number for each "Undefined method 'new' for (Random number)".

Answer

It's a simple mistake. hash calls a method on the current object that returns a number used by the Hash structure for indexing entries, where Hash is the hash class you're probably intending:

foods = Hash.new()

Or more succinctly:

foods = { }

It's ideal to use { } in place of Hash.new unless you need to specify things like defaults, as is the case with:

Hash.new(0)

Where all values are initialized to 0 by default. This can be useful when creating simple counters.

Ruby classes are identified by leading capital letters to avoid confusion like this. Once you get used to the syntax you'll have an easier time spotting mistakes like that.

Note that when writing Ruby code you will almost always omit braces/brackets on empty argument lists. That is x() is expressed simply as x. This keeps code more readable, especially when chaining, like x.y.z instead of x().y().z()

Other things to note include being able to read in all lines with readlines instead of what you have there where you manually compose it. Try:

make_database($stdin.readlines.map(&:chomp))

A more aggressive refactoring of your code looks like this:

def make_database(lines)
  # Define a Hash based on key/value pairs in an Array...
  Hash[
    # ...where these pairs are based on the input lines...
    lines.map do |line|
      # ...which have comma-separated components.
      line.split(',')
    end.reject do |key, flag, _, value|
      # Pick out only those that have the right flag.
      flag.downcase == 'b'
    end.map do |key, flag, _, value|
      # Convert to a simple key/value pair array
      [ key, value ]
    end
  ]
end

That might be a little hard to follow, but once you get the hang of chaining together a series of otherwise simple operations your Ruby code will be a lot more flexible and far easier to read.

Comments