B.4 Putting it all together
Consider a method that is designed like the following (very common in Rails); it has one or two arguments followed by a Hash
in the last position:
"/photos", { :controller => "photos", :action => "index" }) get(
Since the keys are symbols, we can use the new hash syntax:
"/photos", { controller: "photos", action: "index" }) get(
Since the Hash is the final argument to the get()
method, we can drop the curly braces:
"/photos", controller: "photos", action: "index" ) get(
And since there are no order-of-operations concerns, we can drop the parentheses around the arguments:
"/photos", controller: "photos", action: "index" get
Much more concise! Again, I personally prioritize readability far above brevity, so I like making things explicit rather than concise. However, when you are reading Ruby code out in the wild (on Stack Overflow or gem READMEs on GitHub), you will most often encounter code using all of these shortcuts, so you have to know how to read it.
Most importantly, the Rails team has adopted the above optional syntaxes as their default, so when you’re reading the Rails Guides, you will often see things like this:
class Holiday < ApplicationRecord
:name, uniqueness: { scope: :year, message: "should happen once per year" }, presence: true
validates end
Holy moly! What’s going on here? You just have to unwind each optional syntax one by one to get to something more familiar.
First of all, they have dropped the parentheses around the arguments to the validates()
method. We can replace them:
class Holiday < ApplicationRecord
:name, uniqueness: { scope: :year, message: "should happen once per year" }, presence: true)
validates(end
Next: whenever you see a colon at the end of a token, you know it’s the new Hash syntax. So we can unwind it by moving the colons to the beginning and putting back the Hash rockets:
class Holiday < ApplicationRecord
:name, :uniqueness => { :scope => :year, :message => "should happen once per year" }, :presence => true)
validates(end
Notice that I didn’t (and can’t) move the colon in :name
— it’s already at the beginning, and this Symbol is not being used as the key in a Hash. It is simply the first argument to the validates
method. Similarly, the Symbol:year
is the value associated to the key :scope
, so we leave it alone.
Next: the second and last argument to the validates()
method is a Hash with two keys (:uniqueness
and :presence
), so the Rails Guides dropped the curly braces around it. We can put them back:
class Holiday < ApplicationRecord
:name, { :uniqueness => { :scope => :year, :message => "should happen once per year" }, :presence => true })
validates(end
We might also take advantage of whitespace to indent more helpfully:
class Holiday < ApplicationRecord
validates(:name,
{:uniqueness => {
:scope => :year,
:message => "should happen once per year"
},:presence => true
}
)end
Now that we’ve fully unwound the optional syntaxes, it’s easier to see that:
The
validates()
method is taking two arguments; the first is a Symbol and the second is a Hash.The value associated with the
:uniqueness
key is itself another, nested, Hash:{ :scope => :year, :message => "should happen once per year" }
.That Hash has two keys in it:
:scope
and:message
.We cannot drop the curly braces around
{ :scope => :year, :message => "should happen once per year" }
because it is not the last argument to a method — it is the value associated to the key:uniqueness
in a parent hash. If we tried to drop them, we’ll run into problems:class Holiday < ApplicationRecord :name, { :uniqueness => :scope => :year, :message => "should happen once per year", :presence => true }) validates(end
Ruby can’t make sense of
:uniqueness => :scope => :year
, and can’t tell which things are in the inner hash vs. the outer hash.
So: don’t fret when you see seemingly unfamiliar syntax like scope: :year
— it’s nothing new, it’s just a different style. You’ve got the tools you need to understand any Ruby you encounter.