danieljimeneznz
danieljimeneznz

Reputation: 489

`[]': no implicit conversion of Symbol into Integer (TypeError) for a simple ruby Hash

In my ruby code, I define a new hash:

options = {
        host: 'localhost',
        user: nil,
        file: nil,
}

I then parse the hash using the OptionsParser

OptionParser.new do |opts|
    opts.banner = 'Usage: ruby-remote-tailer.rb [options]'

    opts.on('-h', '--host', 'The host to run ssh session with') do |host|
        options[:host] = "#{host}"
    end

    opts.on('-h', '--user', 'The user account that will run the session') do |user|
        options[:user] = "#{user}"
    end

    opts.on('-f', '--file', 'The file to run the tail on') do |file|
        options[:file] = "#{file}"
    end
end

And run:

options = ARGV.parse!
puts options[:host]

The last puts line results in an error no implicit conversion of Symbol into Integer (TypeError). I know that the input I put in is correct as doing p options works. Any ideas on how to fix this?

(Note I would prefer not to use a .each loop as suggested in other answers to get the single values I need).

Thanks.

Upvotes: 2

Views: 995

Answers (1)

aef
aef

Reputation: 4698

You are not using OptionParser correctly in several ways.

When defining the options you have to provide a placeholder for the actual value of the option. If you do not, it will be interpreted as a switch, returning either true or false depending on whether the switch was set.

opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
  options[:host] = "#{host}"
end

Another mistake is that you defined -h for both the host and the user option. You better define a different letter to each of them, you probably intended to have -u for the user anyway.

But the main problem, the one causing the error is that you treat the return value of the #parse! method as if it would return the parsed values. But the return value is actually the remainder of the ARGV array that was not matched by the parser. And because you try to access it like a Hash by asking for an element by Symbol, it complains because array elements are accessed only by Integer values. To fix it, just keep the options reference from before and don't assign the return value to it.

ARGV.parse!

From here on, I will give you some further criticism, but the things I will recommend should not be the reason of any errors:

Additionally you might skip the default nil values you provided in the Hash in the beginning, if you ask a Hash for an undefined key, you will provide you with a nil anyway.

options = {
  host: 'localhost'
}

I'd say calling #parse! on the the command line argument array ARGV, while it seems to be an option to do so, is not very obvious. I'd recommend saving a reference to the parser to a variable and call the method on the parser.

parser = OptionParser.new do |opts|
…
end

parser.parse!(ARGV)

If you want you can even call it without an argument, it will use ARGV by default. But this would again make your code harder to understand in my opinion.

And you can also get rid of the string conversion of the values through interpolation. At least when you are actually getting your values from the command line arguments array ARGV you can be quite sure that all elements will be String objects. However if you intend to feed the parser with other arrays which are not entirely built with string elements, you should keep the conversion.

opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
  options[:host] = host
end

Also please note that there is a very, very widespread convention that you use an of exactly two spaces for each level of indentation in Ruby code. In your examples you use four and eight spaces, which a lot of Rubyists would dislike very much. See the The Ruby styleguide for more information.

Here is a fixed-up version of your code:

#!/usr/bin/env ruby

require 'optionparser'

options = {
  host: 'localhost'
}

parser = OptionParser.new do |opts|
  opts.banner = 'Usage: ruby-remote-tailer.rb [options]'

  opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
    options[:host] = host
  end

  opts.on('-u', '--user USER', 'The user account that will run the session') do |user|
    options[:user] = user
  end

  opts.on('-f', '--file FILE', 'The file to run the tail on') do |file|
    options[:file] = file
  end
end

parser.parse!(ARGV)
puts options[:host]

Upvotes: 1

Related Questions