1

This is a fake banking app:

class Bank
    private def initialize
        $login = Hash.new
        puts "Welcome to HEIDI BANK!"
    end

    def add(keyVal)
        keyVal.each do |key, value|
            $login[key] = value
        end
    end

    def NewUser
        puts "What is the name of the user you would like to add?"
        user = gets.chomp
        puts "What is the password you would add?"
        pass = gets.chomp
        passsym = pass.to_sym
        $login.add(user => passsym)
    end
end

you_bank = Bank.new
you_bank.NewUser

When I attempt to run it, I get:

Welcome to HEIDI BANK!
What is the name of the user you would like to add?
12
What is the password you would add?
13

then an error is raised:

in `NewUser': undefined method `add' for {}:Hash(NoMethodError)

How would I fix this error? It seems I might have to figure out how to call Bank.add or something. Do hashes have an add function built in, like with samp_array.push for arrays?

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • For all wondering, Heidi is my little sister's name. That's why it's called HEIDI bank. – Nathan Tibbitts Nov 02 '18 at 01:03
  • 2
    `$login.merge(user: passsym)`. See [Hash documentation](https://ruby-doc.org/core-2.2.0/Hash.html#method-i-merge). – anothermh Nov 02 '18 at 01:19
  • I read in another post that this would delete old keys and values. Is this true? – Nathan Tibbitts Nov 02 '18 at 01:29
  • 1
    `hash.merge(key: value)` creates a new `Hash` using `hash` as a template that includes the new key-value pair. If the key already exists in `hash` then when `merge` creates a new `Hash` the new `Hash` will have the new key value pair, not the old. This is **all** in the link I included in my previous comment: "_Returns a new hash containing the contents of other_hash and the contents of hsh_" – anothermh Nov 02 '18 at 01:34
  • 1
    Or change `$login.add(user => passsym)` to `self.add(user => passsym)` – halfelf Nov 02 '18 at 01:35
  • @anothermh Sorry, didn't see that you posted a link to the documentation. – Nathan Tibbitts Nov 02 '18 at 01:36
  • You are using a global variable `$login`, which is rewritten every time a `Bank` is created, and all instances of `Bank` will share that. That looks like code smell. – sawa Nov 02 '18 at 07:28

2 Answers2

1

You defined add as an instance method of Bank, but you are calling it on $login, which is an instance of Hash. Call it on the instance of Bank itself, which can be omitted:

def NewUser
  ...
  add(user => passsym)
end
sawa
  • 165,429
  • 45
  • 277
  • 381
1

I already provided the answer in the comments but I wanted to make a longer form answer to help you out. It appears that you're a young programmer and new to Ruby so I would like to help you adopt healthy habits. Accordingly, I have refactored your code and included comments explaining the changes with links to resources that you can read to help understand why the changes were made.

These are mostly minor issues but they're important when writing any kind of production code or when writing code that others may have to read or use in the future.

# Allows the use of STDIN.noecho which will not echo text input back to the console:
# https://stackoverflow.com/a/29334534/3784008
require 'io/console'

# Use two spaces of indentation for Ruby, not tabs and not 4 spaces:
# https://github.com/rubocop-hq/ruby-style-guide#source-code-layout
class Bank
  # The initialize method is not a private method:
  # https://stackoverflow.com/q/1567400/3784008
  def initialize
    # Use single quotes for non-interpolated strings
    # https://github.com/rubocop-hq/ruby-style-guide#strings
    puts 'Welcome to HEIDI BANK!'

    # Don't instantiate the `login` variable here; it should be lazily instantiated:
    # http://blog.jayfields.com/2007/07/ruby-lazily-initialized-attributes.html
  end

  # Use snake_case for method names
  # https://github.com/rubocop-hq/ruby-style-guide#naming
  def new_user
    puts 'What is the name of the user you would like to add?'
    user = gets.chomp

    puts 'What is the password you would add?'
    # Suppress local echo of the password as it is typed
    # https://stackoverflow.com/a/29334534/3784008
    pass = STDIN.noecho(&:gets).chomp

    # Do not call .to_sym on pass; if pass == an Integer then it will raise an exception,
    # e.g., 1.to_sym => NoMethodError: undefined method `to_sym' for 1:Integer
    { user => pass }
  end
end

Then you run it like you were doing before:

you_bank = Bank.new
Welcome to HEIDI BANK!
=> #<Bank:0x00007f8cc9086710>
you_bank.new_user
What is the name of the user you would like to add?
foo
What is the password you would add?
=> {"foo"=>"bar"}

Two other notes on your original code:

  1. Don't use global variables (variable name preceded with a $). There is more explanation at this answer. If you end up needing a variable that is accessible within the instance of the class you should use an instance variable.
  2. Write DRY code. There's no need for the add(keyVal) method because it's implemented already in Hash by merge. Try translating the issue you want to resolve into something you can search for, in this case you want to "add to hash in ruby" and the first Google result for that query is this answer that details how to do that.

I hope this helps you out.

Update

Below you asked "what is lazy instantiation?" The short answer is: don't assign variables until they need to be used. For example:

# Bad; don't do this
class Foo
  def initialize
    # This variable is instantiated when calling `Foo.new`, 
    # before it needs to be used, and so takes up memory right 
    # away vs. only once it's needed
    @variable_used_by_example_method = 'foobar'
  end

  def example_method
    puts @variable_used_by_example_method
  end
end

# Better; okay to do this
class Foo
  def initialize
  end

  def example_method
    # This variable is lazily instantiated, that is, it is not 
    # instantiated until `Foo.new.example_method` is called
    @variable_used_by_example_method = 'foobar'
    puts @variable_used_by_example_method
  end
end

For your other question about comparing usernames and passwords against what users type in, I recommend you think through the problem and if you still are unsure then post a new question. The question you've asked isn't clear enough for me to give you a good answer. Right now you're experimenting with the code to figure out how everything works, and when you're experimenting and learning it's okay to do things that don't squarely fit into object-oriented programming design patterns. But any answer I give you based on what you've told me so far would either go against those patterns, or would be too advanced. (e.g., use a database and associated models in a framework like Rails)

I wouldn't do the first because that would be bad advice, and I wouldn't do the second because you should have a firmer grasp of Ruby and programming first. So my recommendation is for you to think these through:

  1. What is the plain-English step-by-step explanation of your overall goal?
  2. How can that explanation be described in terms of object-oriented logic?
  3. What code can you write to encapsulate that logic?
  4. What are the gaps between your ability to describe the logic and your ability to write code for that logic?

Then you can begin to ask specific questions to fill in those gaps.

anothermh
  • 9,815
  • 3
  • 33
  • 52