Reputation: 69
I used to be more of a hobby Java guy and try to switch to Ruby on Rails right now. But I'm having some difficulties, believe it or not, I like braces and semicolons..gives some orientation.
But here's the question: Right now I'm taking an online course in RoR and it occured to me, that I'm always wrong on how to work with symbols, hashes etc.
Example 1: Let's take a look at this single line of code for example:
form_for(:session, :html => {class: "form-horizontal", role: "form"}, url: login_path)
And this is how I read it:
Method / Function name is
form_for
Parameters parsed to this method are:
:session, :html => {class: "form-horizontal", role: "form"}, url: login_path
Let's break up those into
:session
:html => {class: "form-horizontal", role: "form"}
url: login_path
How the heck should I know how to declare those parameters? Why are :session and :html passend in as keys and url not? Is the :html symbol a Hashmap-symbol?
In an model File you declare an n:m relationship like this (for example users <-> stocks)
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same as
:through => :user_stocks
correct?
But in the same way, let's look at an routes.rb config from the same project:
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct? It does get clearer when writing an question but still, is there a rule of thumb / convention on when to use
:name
name: value
:name => {values}?
name: [values]
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Generally speaking, I'm very confused on how the parameter syntax convention is and when to use what (what type of argument). Is it just because I am starting with Ruby or did I miss some piece of convention.
I hope my problem is kind of understandable and excuse my english - non native speaker. I really like to get along with RoR but right now watching the online course sometimes leaves me more confused than before because if I would've done it by myself, I would've used a completely different way.
Upvotes: 2
Views: 531
Reputation: 48599
How the heck should I know how to declare those parameters?
You look up the method in the docs and you read about it.
Parameters parsed to this method are:
:session, :html => {class: "form-horizontal", role: "form"}, url: login_path
How the heck should I know how to declare those parameters? Why are :session and :html passend in as keys and url not? Is the :html symbol a Hashmap-symbol?
In ruby, if you pass in a series of key-value pairs at the end of the argument list, ruby gathers them all into a hash and passes them as one argument to the method. Here is an example:
def go(x, y)
p x
p y
end
go(:hello, a: 10, b: 20)
--output:--
:hello
{:a=>10, :b=>20}
Another example:
def go(x, y)
p x
p y
end
go(
:session,
:html => {class: "form-horizontal", role: "form"},
url: 'xyz'
)
--output:--
:session
{:html=>{:class=>"form-horizontal", :role=>"form"}, :url=>"xyz"}
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same as
:through => :user_stocks
correct?
Correct. In old ruby, key-value pairs in hashes were written like this:
'a' => 'hello'
If the value was a symbol, then it looked like this:
'a' => :hello
If the key was also a symbol, then you wrote:
:a => :hello
In modern ruby, if the key is a symbol you can write:
a: 'hello'
which is a shortcut for:
:a => 'hello'
and if the value is a symbol as well, in modern ruby it looks like this:
a: :hello
which is a shortcut for:
:a => :hello
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct?
The hash isn't named except
, but otherwise you are correct.
a rule of thumb / convention on when to use
:name # Single symbol argument name: value # A key-value pair in a hash. The key is a symbol. :name => {values}? #A key-value pair in a hash. The value looks like a hash, but the syntax is incorrect. name: [values] #A key-value pair in a hash. The value is your notation for an array.
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Once again, a method can be defined to take any type of argument. Because ruby variables don't have types, you have to check the docs. If a method expects you to pass in a hash where the key :name has a value that is a hash, then you need to do that. On the other hand, if the method expects you to pass in a hash where the key :name has a value that is an array, then you need to do that.
Generally speaking, I'm very confused on how the parameter syntax convention is and when to use what (what type of argument). Is it just because I am starting with Ruby or did I miss some piece of convention.
Ruby has a lot of shortcuts, which can be confusing to a beginner. Then there is the whole String v. Symbol
concept. If you can understand the practical difference between a Symbol and a String, then you will be ahead of the game. A Symbol is like an integer. So when ruby has to compare whether Symbols are equal, ruby compares two integers, which is fast. If ruby has to compare Strings, then ruby has to compare the ascii code of each letter in one string to the ascii code of each letter in the other string until ruby finds a difference. For instance, in order for ruby to compare the following two Strings:
"helloX" v. "helloY"
ruby won't find a difference until after it has made six integer comparisons:
'h' v 'h' => equal
'e' v 'e' => equal
...
...
'X' v 'Y' => not equal
On the other hand, if ruby were comparing:
:helloX v. :helloY
the Symbols are essentially stored as single integers, something like:
341343 v. 134142 => not equal
To compare them takes only a single integer comparison, so it's faster. As someone is sure to point out, that isn't quite how Symbols are implemented, but the details don't matter. It's sufficient to know that Symbol comparisons are faster than String comparisons, and as to why that is true the example above is sufficient to demonstrate there is at least one implementation where it can be true.
Upvotes: 8
Reputation: 102046
In Ruby hashes can use either "hashrockets" or the newer literal notation (since Ruby 1.9):
# hashrockets
{ :foo => "bar" }
# literal
{ foo: "bar" }
They both do the exact same thing. They create a hash with the symbol :foo
as a key. The literal syntax is now generally preferered.
Hashrockets should only be used today if you have something other than symbols as keys (you can have numbers, strings or any object as keys):
{ 1 => 'a', 2 => 'b' }
# using literals won't work here since it will cast the keys to symbols:
{ 1: 'a', 2: 'b' } # => { :1 => 'a', :2 => 'b' }
As Ruby is loosly typed hashes can contain any kinds of values:
{ foo: "bar" }
{ foo: [:bar, :baz] }
{ foo: { bar: :baz } }
In Ruby methods can recieve both ordinal arguments and a hash with options:
def foo(bar, hash = {})
@test= hash[:]
end
The hash options must come after the positional arguments in the arguments list.
# syntax error
foo(a: 2, "test")
# good
foo("test", a: 2)
When passing a hash options you can forgo the surrounding brackets as they are implied:
foo("test", { a: 1, b: 2 })
# same result but nicer to read
foo("test", a: 1, b: 2 )
Ruby 2.0 introduced keyword arguments which reduces the amount of boilerplate code needed to parse the options:
def foo(bar, test: nil)
@test= test
end
def foo(bar, test: nil, **kwargs)
@test= test
# all the options except test
puts kwargs.inspect
end
# will raise an error if the test key is not passed
def foo(bar, test:)
@test= test
end
Postitional arguments are shorter and ideal where the order of the parameters is self explainatory:
class Number
def add(x)
@val += x
end
end
When you have more complex methods with a large number of arguments it can be tricky to keep track of the order:
def book_flight(destination, seats, airline_preference= nil, special_meals= nil)
end
thats where hash options come in and shine as you can have an arbirtrary number of options and the program does not blow up because you forgot a nil in the call.
Upvotes: 1