Monday 23 April 2012

Ruby debugger with Rails

Using the Ruby debugger with Rails will save you oodles of time.
Here’s how to install it, plus a real life example of when you might use it.

3 second setup

1
sudo gem install ruby-debug
To start rails with the debugger, either use:
1
script/server --debugger
Or add something like this to your bash profile:
1
alias ss='script/server --debugger'

Examples

Let’s say we are working in an old application, and want to add some view code that we are only showing if a person can edit a page.
1
 <%= link_to "Edit", edit_page_path(@page) if can_edit_page? %> 
However, unfortunately, the “Edit” link isn’t showing up when we think that it should be, and someone has filed a ticket on us to figure out why.
So, we look at the can_edit_page? method, which was written by someone else a year ago.
1
2
3
4
5
6
7
8
9
10
11
12
def can_edit_page?(*args)
  options = args.extract_options!
  if @user && current_user && @page && !current_user.tall?
    return true if @user.admin?
    if @user.pages.include?(@page) && !options[:eats_chicken]
      if @user.user_pages.find_by_page_id(@page.id).editable?
        return @user.birthday_in_range? && @user.can_dance?
      end
    end
  end
  false
end
While the above is obviously trying to be horrid for the sake of example, I’ve definitely seen worse…so it’s not out of the question.
Now, in this case, all you want to do is have an edit link. The app is poorly tested, which means that you probably shouldn’t try to refactor can_edit_page right away, because it’s used in 100 places.
The issue above is that you aren’t sure which if the bajillion things are not working correctly, and you want to quickly find out.
So, in this case, since the method isn’t working for you, you have a few options:
1) Use raise
You could do something like
1
raise "foo" unless current_user
For each of the cases and statements, until you find the one that is incorrect. This is how I debugged for my first few days of rails…it’s painful and not a good idea.
2) Use the logger
You could add in a ton of logger statements:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def can_edit_page?(*args)
  options = args.extract_options! 
  logger.debug(options) 
  logger.debug(@user)
  logger.debug(current_user)
  logger.debug(@page)
  logger.debug(current_user.tall?)
  if @user && current_user && @page && !current_user.tall?
    logger.debug(@user.admin?)
    # etc
    return true if @user.admin?
    if @user.pages.include?(@page) && !options[:eats_chicken]
      if @user.user_pages.find_by_page_id(@page.id).editable?
        return @user.birthday_in_range? && @user.can_dance?
      end
    end
  end
  false
end
However, this is obviously ugly, and takes forever, plus you have to wade through the log’s other info just to read your statements.
3) Use debugger
The quickest path to finally discovering why the “Edit” link isn’t showing up is to use the debugger.
Simply add “debugger” at the beginning of the method
1
2
3
4
5
6
7
8
9
10
11
12
13
def can_edit_page?(*args)
  debugger # add it here
  options = args.extract_options! 
  if @user && current_user && @page && !current_user.tall?
    return true if @user.admin?
    if @user.pages.include?(@page) && !options[:eats_chicken]
      if @user.user_pages.find_by_page_id(@page.id).editable?
        return @user.birthday_in_range? && @user.can_dance?
      end
    end
  end
  false
end
At the next request, you’ll notice that the your dev server shell now gives the (rdb) prompt.
From here, it’s trivial to figure out what’s going wrong. We can just go through all of the cases, and find which one is screwing us up.
So, for example:
1
2
3
4
5
6
7
8
(rdb) p @user
#=> user info
(rdb) p current_user
#=> user info
(rdb) p @page
#=> page info
(rdb) p !current_user.tall?
#=> false
There it is. So, it turns out that our current user is tall, and the original coder didn’t want tall people to be able to edit pages. We can now decide where to go from here (move on, or refactor).
Note that in real life this kind of debugger use takes about 5 seconds, which is much much faster than logger debugger (ie printf debugging).
So, do consider using debugger the next time you run into a bug that makes you want to use the logger.
Notes:
0) Check out the Rails Debugging Guide for a full description of different rails debugging methods.
1) The debugger is much more featureful than I show above. If you’re familiar with gdb, it has many of the same features. You can continue, break, print, list, go to the next line, etc.
2) Adding debugger to the end of methods is not good, because your method will return “debugger”
For methods that return nothing, put a nil at the end of the method after debugger, or put whatever it was supposed to return after debugger in a temp.
 http://www.themomorohoax.com/2009/02/09/use-debugger

http://guides.rubyonrails.org/debugging_rails_applications.html


No comments:

Post a Comment