Search code examples
rubyobjectparameter-passingruby-2.3

ruby parameters returning themselves


I'm running Ruby 2.3.1 x64 on Windows 10 x64.

My code:

class Credentials

  attr_reader :username, :password

    def initialize(username = nil, password = nil)
        @username = username
        @password = password
        get_credentials if !@username || !@password #Gets credentials if none are specified
    end

  def get_credentials
    @username = ask("Username:  ") { |q| q.echo = true }
    @password = ask("Password:  ") { |q| q.echo = "*" }
  end

end

Ignore the get_credentials wackyness, it's a gem called Highline that I'm using to hide input for security reasons.

When I do the following:

$user = Credentials.new(username: "foo", password: "bar")

I get this return:

#<Credentials:0x000000038ecf30 @password=nil, @username={:username=>"foo", :password=>"bar"}>

Likewise, calling $user.username returns the following:

{:username=>"foo", :password=>"bar"}

when it should be returning: "foo"

and calling $user.password returns nil.

Can someone tell me why in the name of Henry Hamilton this is happening?! I've used hashed parameters many times, and it always works just fine. Why is it stuffing every parameter setting into a single parameter?


Solution

  • When you define a method/constructor you don't pass arguments by name but by value just like any other programming language, So :

    $user=Credentials.new("foo","bar")
    

    Will do what you want.

    This is the default in almost every programming language, your question should have been "How did this work", it worked because ruby is dynamically typed and the syntax key1: val1,key2: val2,... is the new hash syntax(since ruby 1.9), a hash is a key-value data structure , so your :

    $user=Credentials.new(username: 'foo',password: 'bar')
    

    Is actually calling the constructor with one argument only which is username with the value {username: 'foo',password: 'bar'} and because initialize is defined with default arguments , password got a value of nil.

    Now if you do want to pass arguments by name, you have to define the constructor like so :

    def initialize(username: nil,password: nil)
    //code
    end
    

    After that you can do :

    $user=Credentials.new(username: 'foo',password: 'bar')
    

    And expect it to behave like you want.

    Notice that keyword arguments(that is passing arguments by name) are introduced in ruby 2, also notice that you can achieve the same with a constructor that accepts one parameter which is a hash like this :

    def initialize(params={})
    //code
    end
    

    But this way doesn't limit the number of arguments nor their names(you can call Credentials.new(fooprop: 'foovalue') and no error will be thrown), also it needs some change in code.

    The Keyword arguments feature is found in some programming languages and it's useful when the function have many parameters or to make it clear for the programmer what is the parameter for.