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:

get("/photos", { :controller => "photos", :action => "index" })

Since the keys are symbols, we can use the new hash syntax:

get("/photos", { controller: "photos", action: "index" })

Since the Hash is the final argument to the get() method, we can drop the curly braces:

get("/photos", controller: "photos", action: "index" )

And since there are no order-of-operations concerns, we can drop the parentheses around the arguments:

get "/photos", controller: "photos", action: "index"

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
  validates :name, uniqueness: { scope: :year, message: "should happen once per year" }, presence: true
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
  validates(:name, uniqueness: { scope: :year, message: "should happen once per year" }, presence: true)
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
  validates(:name, :uniqueness => { :scope => :year, :message => "should happen once per year" }, :presence => true)
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
  validates(:name, { :uniqueness => { :scope => :year, :message => "should happen once per year" }, :presence => true })
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
      validates(:name, { :uniqueness => :scope => :year, :message => "should happen once per year", :presence => true })
    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.