Search code examples
rubyyamlnomethoderror

nil:NilClass NoMethodError in MD5 program


I'm creating a tool that will turn passwords into MD5 hashes within a YAML file, while running my program I'm getting the error:

md5.rb:20:in `encrypt_md5': undefined method `[]=' for nil:NilClass (NoMethodError)
        from main.rb:12:in `main'
        from main.rb:40:in `<main>'

From this method:

def encrypt_md5
    hash = load_file
    hash[:users][:"#{prompt('Enter username')}"] = 
        {password: prompt("Enter password")}
    save_file(hash)
    exit unless again?('encrypt md5')
end
#Digest::MD5 hasn't been implemented yet

I'm not entirely sure what is happening that's causing the nil:NilClass NoMethodError, can somebody explain to me what this means and also tell me what I need to change in order for it to work..

Main.rb source:

require_relative 'md5.rb'

def main
     puts <<-END.gsub(/^\s*>/, '')
                >
                >To load information type "L" to quit system type "Q"
                >
            END
    input = gets.chomp.upcase
    case input
    when "L"
        encrypt_md5
    when "Q"
        exit_system
    else
        exit_lock
    end
end

def exit_system
    puts "Exiting..."
    exit
end

def exit_lock #Not finished, will lock user out of program
    puts "Locked out, please contact system administrator"
    exit
end

def again? #Not complete
    puts "Encrypt more?"
    input = gets.chomp
    return input =~ /yes/i
end

def prompt( message )
    puts message
    gets.chomp
end
main

md5.rb source:

require 'yaml'
require 'digest'

private

    def load_file
        File.exist?('info.yml') ? YAML.load_file('info.yml') : {passwords: {}}
    end

    def read_file
        File.read('info.yml')
    end

    def save_file
        File.open('info.yml', 'w') { |s| s.write('info.yml')}
    end

    def encrypt_md5
        hash = load_file
        hash[:users][:"#{prompt('Enter username')}"] = 
            {password: prompt("Enter password")}
        save_file(hash)
        exit unless again?('encrypt md5')
    end

Solution

  • The error is probably here:

    hash = load_file
    hash[:users][:"#{prompt('Enter username')}"] = 
      { password: prompt("Enter password") }
    

    You're calling the []= method on hash[:users], but hash[:users] is nil. Since you didn't post the contents of info.yml, I can only guess that you have a YAML file like this:

    users:
      some_user:
        password: foobar
      another_user:
        password: abcxyz
      # ...
    

    When you do YAML.load_file on that file, you get this Hash:

    hash = {
      "users" => {
        "some_user" => { "password" => "foobar" },
        "another_user" => { "password" => "abcxyz" },
        # ...
      }
    }
    

    With this, hash[:users] is nil because there's no :users key, there's only a "users" key. While you could jump through some hoops to turn your keys into symbols, it will be far easier to just use string keys everywhere:

    hash = load_file
    hash["users"][prompt('Enter username')] =
        { "password" => prompt("Enter password") }
    

    P.S. There are some more problems with your code, in particular here:

    def save_file
        File.open('info.yml', 'w') { |s| s.write('info.yml')}
    end
    

    Firstly, you call save_file(hash), but your save_file method doesn't take any arguments. This will raise an ArgumentError. Secondly, what the code in this method does is open the file info.yml and then write the string "info.yml" to that file. What you probably want is something like this:

    def save_file(hash)
        File.open('info.yml', 'w') {|f| f.write(hash.to_yaml) }
    end