In Progress
Unit 1, Lesson 1
In Progress

Symbol Placeholder

Video transcript & code

Remember back in episode 108, we talked about the trouble with nil? Today I want to show you one of the simplest and easiest tricks I know for getting rid of a nil and replacing it with something more meaningful.

Here's a made-up method which contacts a web service in order to get a list of widgets. The web service in use has some restrictions placed upon it: non-authenticated users may only request 20 results or less at a time. If we want to get more than 20 widgets per page of results, we have to supply a username and password.

def list_widgets(options={})
  credentials = options[:credentials]
  page_size   = options.fetch(:page_size) { 20 }
  page        = options.fetch(:page) { 1 }

  if page_size > 20
    user     = credentials.fetch(:user)
    password = credentials.fetch(:password)
    url = "https://#{user}:#{password}@" +
      "www.example.com/widgets?page=#{page}&page_size=#{page_size}"
  else
    url = "http://www.example.com/widgets" +
      "?page=#{page}&page_size=#{page_size}"
  end

  puts "Contacting #{url}..."
end

But this method tries to be user-friendly. So long as we don't specifically set a higher page size, it uses a non-authorized URL. We can simply call it with no arguments.

list_widgets

If we call it with a bigger page size, we need to pass in some auth credentials as well:

list_widgets(
        page_size: 50,
        credentials: {user: 'avdi', password: 'xyzzy'})

Ah, but what happens when we call it with a larger page size and no auth credentials?

list_widgets(page_size: 50)

This time, we get an error. And not just any error: we get the dreaded NoMethodError for nil. We never like seeing this error, because it means playing the game every Ruby developer learns to hate: the game of whack-a-nil! Since nil is ubiquitous and can't tell where it originated, it's often hard to pinpoint exactly why we're getting a NoMethodError.

Let's make one tiny change to this method. Instead of implcitly defaulting the :credentials to nil, we'll explicitly default them to the symbol :credentials_not_set.

def list_widgets(options={})
  credentials = options.fetch(:credentials) { :credentials_not_set }
  page_size   = options.fetch(:page_size) { 20 }
  page        = options.fetch(:page) { 1 }

  if page_size > 20
    user     = credentials.fetch(:user)
    password = credentials.fetch(:password)
    url = "https://#{user}:#{password}" +
      "@www.example.com/widgets?page=#{page}&page_size=#{page_size}"
  else
    url = "http://www.example.com/widgets" +
      "?page=#{page}&page_size=#{page_size}"
  end

  puts "Contacting #{url}..."
end

Now let's try that method call again.

-:7:in `list_widgets': undefined method `fetch' for 
  :credentials_not_set:Symbol (NoMethodError)
from -:19:in `<main>'

Can you see the difference in this error? This time, instead of NoMethodError on nil, we have a NoMethodError on the :credentials_not_set symbol. This has two big advantages:

First, it's an immediate clue about what we did wrong—it says right there, we didn't set the auth credentials! And second, it gives us a string we can grep for to find out exactly where that unexpected object came from.

There are a lot of other ways to handle communication to the client coder that something needed is missing. But as far as bang for the buck goes, it doesn't get much better than replacing a nil value with a placeholder symbol. With just a tiny change we've potentially saved the next coder to call this method a lot of effort.

So the next time you write a method with optional parameters, or a class with optional attributes, consider defaulting those parameters to something a little more meaningful than nil. Happy hacking!

Responses