November 18, 2010

#24 BDD With RSpec - Exception Handling

In the Post #23, we created a 'tweet' method for our controllers in order to handle exceptions from the Twitter gem. Here, we use some behavior driven development to generalize our exception handling.

Setting Up The Test Suite

We will develop the code outside of our Rails app and use RSpec-2 with Autotest. (See the RSpec wiki for Autotest setup. Also, if you haven't read the Cucumber features for RSpec, I would recommend reading them at Relish.)

We create a new directory, rescue_controllers, for our project. From this directory, we can configure RSpec to use autotest using "rspec --configure autotest." This will create an autotest/discover.rb file. Our working code will live in the lib directory. Taking cues from the previous gems that we have looked at, we will have a rescue_controllers folder and a rescue_controllers.rb file in the lib directory. Most of our code will reside in rescue.rb.

We will put our tests for rescue.rb in spec/rescue_controllers/rescue_spec.rb. We also create a spec_helper file. This file will require rescue_controllers.rb. Also, we setup some RSpec configuration here. We configure RSpec to stop running additional tests if one test fails ('fail fast'). Also, we'll make it so that we can choose to run or exclude a specific test or group of tests ('filter run').
Terminal and spec_helper.rb
# Terminal

> gem install rspec autotest

> mkdir rescue_controllers
> cd rescue_controllers

# The rest of these commands happen from rescue-controllers/

> rspec --configure autotest

> mkdir lib
> mkdir lib/rescue_controllers
> touch lib/rescue_controllers/rescue.rb
> touch rescue_controllers.rb
> echo "require 'rescue_controllers/rescue'" > lib/rescue_controllers.rb

> mkdir spec
> mkdir spec/rescue_controllers
> touch spec/rescue_controllers/rescue_spec.rb
> touch spec/spec_helper.rb
________________________________________

# spec_helper.rb

require 'rescue_controllers'

RSpec.configure do |c|
  c.fail_fast = true
  c.filter_run :focus => true
  c.filter_run_excluding :broken => true
  c.run_all_when_everything_filtered = true
end

Defining The Problem

Taking a broader look at the problem, we want to be able to prevent our app from crashing in the event that a method inside a controller throws an exception. In addition, we want to have some flash messages available to tell us if an exception did or did not occurred. If an exception did occur, we'll want to save that in an instance variable for possible use in the controller.

In order to rescue an exception, we have to wrap the method throwing the exception in a begin/end block with a rescue clause to catch the exception. That means we'll need to create a method, rescue_me(), that will surround (accept as an argument) the method causing the exception. However, we can't just do rescue_me(bad_method()) because here bad_method() isn't really being wrapped by rescue_me(). rescue_me() only receives the result of bad_method(), and by that time, it's too late. (Snippet #1)

What we need instead is to package up bad_method() and defer its evaluation* until rescue_me() is ready to catch the exception. The answer here lies in blocks and Procs. If we wrap bad_method() in a block and send that to rescue_me(), then we can catch the exception. In fact, we don't even have to run bad_method(). (Snippet #2)

In Snippet #3, when we put that ampersand in front of the 'block' argument, we're telling Ruby "Our method is getting a block. Please run the code in the block when I call 'yield.'" Behind the scenes, when we send that block to rescue_me(), the block is being converted to a Proc object (another kind of container for code). The other way to run the code in 'block' is to execute the Proc's #call method. (Snippet #4. Notice that the '&' is left off when referring to 'block' as a Proc.)

(* Deferred Evaluation - "Store a piece of code and its context in a proc or lambda for evaluation later."
This term comes from the book "Metaprogramming Ruby" by Perrotta.)
IRB
# #1
> def rescue_me(arg)
>   puts "oh no you didn't!"
> end
>
> rescue_me(raise 'badness')
  => RuntimeError: badness

# #2
> def rescue_me(&block)
>   puts "oh no you didn't!"
> end
>
> rescue_me{ raise 'badness' }
  => "oh no you didn't!"

# #3
> def rescue_me(&block)
>   puts "#{block.class}"
>   yield
>   "oh no you didn't!"
> end
>
> rescue_me{ raise 'badness' }
  => Proc
  => RuntimeError: badness

# #4
> def rescue_me(&block)
>   block.call
>   "oh no you didn't!"
> end
>
> rescue_me{ raise 'badness' }
  => RuntimeError: badness

Our Controller Test Double

Since we are working outside of Rails, we don't have access to any of the controller classes. That's ok. The only real functionality of a Rails controller that we need access to right now is the flash hash. So we'll create a simple fake controller class and put it in our spec_helper file. This will include our yet to be defined module RescueControllers and accessors for the flash variable. When we create a new instance of Controller, we will initialize our flash variable to an empty hash.

In rescue.rb, we'll have a setup for each test that creates a new Controller instance. After this, we start up Autotest in our terminal.
spec/spec_helper.rb and Terminal
# spec_helper.rb

require 'rescue_controllers'

class Controller  
  include RescueControllers
  
  attr_accessor :flash
  
  def initialize
    @flash = {}
  end
end
________________________________________

# rescue_spec.rb

describe RescueControllers do
  before(:each) do
    @controller = Controller.new
  end
end
________________________________________

# Terminal

> autotest

Dealing With The Exceptions

Our first 'context' will be when we pass a block that raises an exception to #rescue_me. ('context' is an alias for 'describe' in RSpec. We could just use 'describe', but to me 'context' makes a little more sense when I read the code.) Let's say that instead of an exception, the result will be nil. To get this test to pass we'll just return nil. (Snippet #1)

For the next test, we want to have a flash message telling us that there was an error. Easy enough. (Snippet #2)

Next, we'll want to save the exception in an instance variable so that we have access to it in the controller. So we write a test for the attribute reader and writer. This test is placed outside of the 'describe "#rescue_me" do' block. (Snippet #3)

Now, we want some information on what the actual exception was. Since, we are repeating '@controller.rescue_me...' again, we'll refactor this into a 'before' hook. In #rescue_me, we'll have to call the "block" Proc object and rescue the exception. Then we assign @exception to the Exception object, 'e'. (Snippet #4. Since we're in a method definition, we don't have to use 'begin'.)

Lastly, we want to make sure that the flash for the successful request is not present. Since we haven't dealt with that yet, this test passes automatically. (Snippet #5)
spec/rescue_controllers/rescue_spec.rb and lib/rescue_controllers/rescue.rb
# #1
# rescue_spec.rb
require 'spec_helper'

describe RescueControllers do
  before(:each) do
    @controller = Controller.new
  end
  
  describe "#rescue_me" do
    context "given an exception is raised" do
      it "returns nil" do
        @controller.rescue_me{ raise Exception }.should be_nil
      end
    end
  end
end
________________________________________
# rescue.rb
module RescueControllers
  def rescue_me(&block)
    nil
  end
end

##############################################################
# #2
# rescue_spec.rb
      it "sets the rescue_alert flash to the default error message" do
        @controller.rescue_me{ raise Exception }
        @controller.flash[:rescue_alert].should eql "There was a problem with the request."
      end
________________________________________
# rescue.rb
  def rescue_me(&block)
    flash[:rescue_alert] = "There was a problem with the request."
    nil
  end

##############################################################
# #3
# rescue_spec.rb
  it "has an @exception setter and getter" do
    @controller.exception = Exception
    @controller.exception.should eql Exception
  end
  
  describe "#rescue_me" do
    ...
________________________________________
# rescue.rb
module RescueControllers
  attr_accessor :exception
  ...

##############################################################
# #4
# rescue_spec.rb
  describe "#rescue_me" do    
    context "given a block that raises an exception" do
      before(:each) do
        @response = @controller.rescue_me{ raise Exception }      
      end

      it "sets @exception" do
        @controller.exception.should be_an_instance_of Exception
      end
      
      it "sets the rescue_alert flash to the default error message" do
        @controller.flash[:rescue_alert].should eql "There was a problem with the request."
      end
      
      it "returns nil" do
        @response.should be_nil
      end
    end
  end
________________________________________
# rescue.rb
  def rescue_me(&block)
    block.call
  rescue Exception => e
    self.exception = e
    flash[:rescue_alert] = "There was a problem with the request."
    nil
  end

##############################################################
# #5
# rescue_spec.rb
      it "the rescue_notice flash should not be set" do
        @controller.flash[:rescue_notice].should_not be
      end

Dealing With Non-Exceptions

Our next 'context' is when our block doesn't raise an exception. In this case, we want to get the result of the call to 'block'. Our existing code passes this test, so we don't have to modify rescue.rb. (Snippet #1)

Next, we want to ensure that there is a flash message stating that the request was successful. We set the method_notice flash after the call to the block, so that it doesn't get set if the block raises an error. However, now one of our other tests ("it 'returns the result of the block'") fails. To fix this, we save the result of block.call and put it after our flash[:method_notice] assignment. (Snippet #2)

Lastly, we require that flash[:method_alert] and @exception are nil. These pass without extra code. (Snippet #3)

For a different view of our test results, we can use RSpec's documentation formating option.
spec/rescue_controllers/rescue_spec.rb, lib/rescue_controllers/rescue.rb and Terminal
# #1
# rescue_spec.rb
  describe "#rescue_me" do
    context "given an exception is not raised" do
      before(:each) do
        @response = @controller.rescue_me{ 'greatness' }              
      end
      
      it "returns the result of the block" do
        @response.should eql "greatness"
      end
    end

##############################################################
# #2
# rescue_spec.rb
      it "sets the rescue_notice flash to the default success message" do
        @controller.flash[:rescue_notice].should eql "The request was successfully submitted."    
      end
________________________________________
# rescue.rb
  def rescue_me(&block)
    response = block.call
    flash[:rescue_notice] = "The request was successfully submitted."
    response
  rescue Exception => e
    ...

##############################################################
# #3
# rescue_spec.rb
      it "@exception should not be set" do
        @controller.exception.should_not be
      end

      it "the rescue_alert flash should not be set" do
       @controller.flash[:rescue_alert].should_not be      
      end
________________________________________

# Terminal

> rspec spec --format documentation

RescueControllers
  has an @exception setter and getter
  #rescue_me
    given an exception is not raised
      @exception should not be set
      the rescue_alert flash should not be set
      sets the rescue_notice flash to the default success message
      returns the result of the block
    given an exception is raised
      the rescue_notice flash should not be set
      sets @exception
      sets the rescue_alert flash to the default error message
      returns nil

The End Result

If we now put rescue.rb in the lib directory of our Rails app, we can use it in our PostsController and AnnouncementsController (not shown). However in the next post we'll dip into some metaprogramming techniques to modify our RescueControllers module a little more.
lib/rescue.rb, app/controllers/posts_controller.rb
# rescue.rb

module RescueControllers
  attr_accessor :exception
  
  def rescue_me(&block)
    response = block.call
    flash[:rescue_notice] = "The request was successfully submitted."
    response
  rescue Exception => e
    self.exception = e
    flash[:rescue_alert] = "There was a problem with the request."
    nil
  end
end
__________________________________________________

# posts_controller.rb

require 'rescue'

class PostsController < ApplicationController
  include RescueControllers
  ...
  def update
    @post = Post.find(params[:id])
    old_status = @post.status

    if @post.update_attributes(params[:post])
      if @post.newly_published?(old_status)
        rescue_me do
          Twitter.update "A new post is available: '##{@post.sequence} #{@post.title}'   http://www.arailsdemo.com/posts/#{@post.sequence}")
        end
        flash[:exception] = " #{exception.to_s}" if self.exception
      end
      redirect_to(post_url(@post.sequence), :notice => "Post was successfully updated. #{response}") }
    else
      render :action => "edit"
    end
  end
  ...

    Comments

(Please login to submit comments.)



View All Posts