Logic Branches Fundamental

Having as few branches of logic as possible is a development fundamental, but forgetting to take advantage of it is easy. Here is an example.

In Rails you can redirect back to the page the user came from like this:

def action
  redirect_to :back
end

However, if the user came to the page directly and does not have an HTTP_REFERER header, Rails will raise a RedirectBackError. The easiest way to prevent this exception is to check for a referer before redirecting back.

def action
  redirect_to request.env['HTTP_REFERER'] ? :back : some_default_url
end

Although checking the request header adds a little noise to this method, it solves the problem of receiving the exception if the referer header is missing. Now, let's look at testing this method. How many tests do we have to write for this one line? Two. We need a test to make sure the method redirects back if the referer is present, and we need a test to make sure the method redirects to some_default_url is the referer is missing.

def test_action_redirects_back_if_referer_is_present
  @request.env['HTTP_REFERER'] = "some url"
  get :action
  assert_redirected_to :back
end

def test_action_redirects_to_some_default_url_if_referer_is_missing
  get :action
  assert_redirected_to some_default_url
end

To reduce this to a single test we need to change the method to have a single branch of logic. We can do this by creating a method to handle the referer checking.

def action
  redirect_back_or_to some_default_url
end

protected

def redirect_back_to_to(url)
  redirect_to request.env['HTTP_REFERER'] ? :back : url
end

Although we still need two tests for the redirect_back_or_to method, we can test the action like this:

def test_action_redirects_back_or_to_some_default_url
  @controller.expects(:redirect_back_or_to).with(some_default_url)
  get :action
end

In addition to reducing the number of tests we need from two to one, pulling out the redirect_back_or_to method keeps our code DRY for anytime we want to avoid a RedirectBackError.