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?
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.