Two screencasts, two ways to eradicate Ruby nil values

nil values are the worst. They crop up where you didn’t expect them. Their presence forces you to litter your code with conditionals: checking, checking, and checking again if a variable contains nil .

Today I’m making available a classic screencast from the RubyTapas archives. In it you’ll see a way to remove nils while dramatically simplifying some typical web application logic, using the Special Case Pattern.

But! There’s more. Today by special permission I’m also bringing you a video from the Upcase Weekly Iteration archives! In this bonus video, you’ll watch Ben Orenstein and Joe Ferris apply a variant of the Special Case pattern, the Null Object pattern, in order to clean up some Rails 4 code.

Enjoy!

 

So that’s the Special Case pattern.

Sometimes, you have a special case where the “special” behavior you need is defined as: “always do nothing”. There’s a name for this “special case of Special Case”: the Null Object Pattern.

Now here’s Ben Orenstein and Joe Ferris of Upcase, with a demonstration of how to apply the Null Object pattern to a Rails project:

 

 

Did you enjoy this pairing of short & sweet RubyTapas with longer, more Rails-centric Upcase content?

If so, I have some very exciting news for you:

This week only, you can get an entire year of RubyTapas AND Upcase for a fantastic price. Go here to find out more.

There are also some nifty bonuses to be had, as well as special deals for teams.

I’ve never done a bundle like this before (and I don’t know if/when I’ll do it again). The deal expires February 6, 2017. If you want to supercharge your Ruby, Rails, object design, refactoring, and testing skills this year… go check it out!


Episode Script

For those who prefer reading to watching. This is for the RubyTapas episode only, not the Upcase video.

Here’s a typical helper method from a web application. It looks to find a current user by checking in a session object. If it can find one it returns it; otherwise it implicitly returns nil.

def current_user
  if session[:user_id]
    User.find(session[:user_id])
  end
end

Let’s look at some places where this method is used.

Here’s some code to greet the user when they first arrive at the site. It checks to see if there is a current user. If so it uses their name; otherwise it uses the name “guest”.

def greeting
  "Hello, " + 
    current_user ? current_user.name : "guest" +
    ", how are you today?"
end

Here’s a case where we render either a “log in” or “log out” button depending on whether there is a user.

if current_user
  render_logout_button
else
  render_login_button
end

Here’s some code that optionally renders an admin panel. Before it can check to see if the user is an admin, it first has to check that there is a user at all.

if current_user && current_user.has_role?(:admin)
  render_admin_panel
end

In some cases we might want to show different results depending on what a user is allowed to see. Once again, we switch on the presence of a user object.

if current_user
  @listings = current_user.visible_listings
else
  @listings = Listing.publicly_visible
end
# ...

So far we’ve just been querying the user object. Here’s some code that updates an attribute on the user. But first it has to check that the user is non-nil.

if current_user
  current_user.last_seen_online = Time.now  
end

Here’s a snippet of code that adds a product to the user’s shopping cart. If they are logged in, it should go into their persistent user cart. Otherwise it should go into a special session-based cart.

cart = if current_user
         current_user.cart
       else
         SessionCart.new(session)
       end
cart.add_item(some_item, 1)

All of the code we’ve been looking at has a common characteristic: it’s uncertain about whether there will be a user object available. As a result, it keeps checking over and over again.

Let’s see if we can get rid of this uncertainty. Instead of representing an anonymous session as a nil value, let’s write a class to represent that case. We’ll call it GuestUser.

class GuestUser
  def initialize(session)
    @session = session
  end
end

We rewrite the #current_user method to return an instance of this class when there is no :user_id recorded.

def current_user
  if session[:user_id]
    User.find(session[:user_id])
  else
    GuestUser.new(session)
  end
end

We add a #name attribute to GuestUser.

class GuestUser
  # ...
  def name
    "Anonymous"
  end
end

This nicely simplifies the greeting code.

def greeting
  "Hello, #{current_user.name}, how are you today?"
end

In the case where we render either “Log in” or “Log out” buttons, we can’t get rid of the conditional completely. But what we can do is add an #authenticated? predicate method to both User and GuestUser.

class User
  def authenticated?
    true
  end
  # ...
end

class GuestUser
  # ...
  def authenticated?
    false
  end
end

By using this predicate method in the code for rendering the button, we end up with code that states its intent a little better.

if current_user.authenticated?
  render_logout_button
else
  render_login_button
end

Next let’s take a look at the case where we check if the user has admin privileges. We add an implementation of #has_role? to GuestUser. Since an anonymous user has no special privileges, we make it return false for any role given.

class GuestUser
  # ...
  def has_role?(role)
    false
  end
end

This simplifies the role-checking code. No more check to see if the current user exists.

if current_user.has_role?(:admin)
  render_admin_panel
end

Now what about the code for showing different listings to different people? We implement a #visible_listings method on GuestUser which simply returns the publicly-visible result set.

class GuestUser
  # ...
  def visible_listings
    Listing.publicly_visible
  end
end

Then we can reduce the listings code to a one-liner.

@listings = current_user.visible_listings

We also implement attribute setter methods as no-ops.

  class GuestUser
    # ...
    def last_seen_online=(time)
      # NOOP
    end
o  end

This enables us to eliminate another conditional.

current_user.last_seen_online = Time.now

In order to implement a shopping cart for users who haven’t yet logged in, we make the GuestUser‘s cart attribute return an instance of the SessionCart type that we talked about earlier.

class GuestUser
  # ...
  def cart
    SessionCart.new(@session)
  end
end

Now the code for adding an item to the cart also becomes a one-liner.

current_user.cart.add_item(some_item, 1)

What we’ve done here is identify a special case—the case where there is no logged-in user. And then we represented that special case as an object in its own right. As a result, we were able to simplify quite a bit of our code. And it’s not just simpler—it reads better too!

There’s a name for this, and unsurprisingly it is the “Special Case” pattern. It’s one application of a more broad observation: anytime a program keeps switching on the same condition over and over again, that’s a good indication that there’s a new kind of object waiting and wanting to be discovered.

That’s it for today. Happy hacking!