Chapter 13. Debugging

I’ve been told that not all developers are perfect like you and me. We never make mistakes in our code, but sometimes other developers make mistakes that we have to clean up. When that happens, the debugging features built into Rails come in handy. This chapter covers those built-in debugging features, starting with the debug helper method, which makes it easier to see the values of variables in your application’s views.

We spent some time looking at the Rails log in previous chapters. In this chapter, you’ll also see how to add your own messages to that log. Finally, using the debugger gem, you can step inside your application as it’s running to track down really tough bugs.

The debug Helper

Rails includes a view helper method called debug that you can use to display the value of an instance variable or method call available inside a Rails view. This helper wraps its output in <pre> tags so it’s easier to read.

For example, let’s see how the output of the current_user method changes as you move through the application. First edit app/views/layouts/application.html.erb and add a call to the debug helper just below the yield method, as shown here:

<!DOCTYPE html>
<html>
--snip--

    <%= yield %>

    <%= debug current_user %>
  </div>
</body>
</html>

Now start the Rails server, if it’s not already running, and go to http://localhost:3000/login in your browser. You should see the output from the debug helper just below the Log In button, as shown in Figure 13-1.

Debugging current_user

Figure 13-1. Debugging current_user

At this point, the output is simply three dashes on one line followed by three dots on the next line. The debug helper is using YAML to format its output. YAML is a data serialization language used frequently in Rails projects. For example, the Rails database configuration file (config/database.yml) is in YAML format. You also used YAML in Chapter 10 to define fixtures that provide default data for tests.

In YAML, the three dashes signify the beginning of a document. Three dots indicate the end of a YAML document. In other words, this is an empty YAML document. On the Log In page current_user is nil, and the empty YAML document reflects that.

Now log in to your application and scroll to the bottom of the posts index page to see how the output from current_user changed.

➊ --- !ruby/object:User
➋ attributes:
    id: 1
    name: Alice
    email: [email protected]
    created_at: 2014-02-26 ...
    updated_at: 2014-02-26 ...
    password_digest: "$2a$10$7..."

Now the YAML output is a little more fleshed out. The first line starts with three dashes followed by !ruby/object:User ➊, which represents the type of object being shown. In this case, the object is a Ruby object of class User. The word attributes ➋ represents the start of the object’s attributes and their values. Below that, you see the User model attributes: id, name, email, created_at, updated_at, and password_digest.

Displaying this information is a great way to monitor the state of your application as it runs. Unfortunately, using the debug helper limits you to seeing values only for your current session, and if your application renders nothing in the browser window, you won’t be able to see any values at all. In those cases, you can rely on the Rails log to track down bugs.

The Rails Logger

Throughout this book, I’ve talked about Rails server output. As the Rails server runs, it shows a copy of the development log. You can open the file log/development.log in your editor to examine that log even when the server is not running.

This file may be quite large depending on how much you’ve been using the application the log belongs to. You can use the bin/rake log:clear command to clear your application’s log files.

Log Levels

The Rails logger uses levels named :debug, :info, :warn, :error, :fatal, and :unknown. These levels indicate the severity of the message being logged. The level is assigned by the developer when a message is logged.

If the level is equal to or higher than the log level configured for the current environment, the message is added to the corresponding log file. The default log level in the development and test environments is :debug and above, and the default log level in the production environment is :info and above.

Because the default log level in production does not display the :debug level, you can leave these debug messages in your code without worrying about cluttering up the logs when your application is deployed and running.

Logging

Each of the log levels has a corresponding method used to print messages. For example, you can call logger.debug "Message" to add a message with the level :debug to the log.

You’ve already seen how to use the debug helper to show values in views. Rails logger messages are typically used in models and controllers.

Let’s add the value of current_user to the log and compare it to what is shown in the browser. Open the file app/controllers/posts_controller.rb in your editor and add the logger statement shown here to the PostsController:

  class PostsController < ApplicationController
    before_action :authenticate_user!

    def index
➊     logger.debug current_user

      user_ids = current_user.timeline_user_ids
  --snip--

This line ➊ adds the output of current_user to the development log every time the posts index action is called. Refresh the page in your browser and examine the log output in your terminal:

  Started GET "/" for 127.0.0.1 at 2014-04-05 19:34:03 -0500
  Processing by PostsController#index as HTML
    User Load (0.1ms) SELECT "users".* FROM "users"
      WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
➊ #<User:0x007fd3c94d4e10>
     (0.1ms) SELECT "users".id FROM "users" ...
  --snip--
    Rendered posts/index.html.erb within layouts/application (27.1ms)
  Completed 200 OK in 61ms (Views: 35.9ms | ActiveRecord: 1.7ms)

The logger.debug converts the value of the current_user method to a string and adds it to the log as #<User:0x007fd3c94d4e10> ➊. Unfortunately, when a Ruby object like current_user is converted to a string, the default representation is the object’s class followed by its object_id.

What you want to do is inspect the object. The inspect method displays attributes and values when called on a Rails model. Change the call to current_user that you just added to the PostsController to current_user.inspect and refresh the page in your browser again.

 Started GET "/" for 127.0.0.1 at 2014-04-05 19:34:27 -0500
 Processing by PostsController#index as HTML
   User Load (0.1ms) SELECT "users".* FROM "users"
     WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
➊ #<User id: 1, name: "User One", ...>
     (0.1ms) SELECT "users".id FROM "users" ...
  --snip--
    Rendered posts/index.html.erb within layouts/application (27.1ms)
  Completed 200 OK in 63ms (Views: 40.9ms | ActiveRecord: 1.7ms)

This output is much better. The value of current_user is shown ➊ with all attributes, just as it appears in the Rails console. The Rails logger displays any string you send to it. I sometimes label the data that I’m logging and add characters like stars to make the data stand out more:

class PostsController < ApplicationController
  before_action :authenticate_user!

  def index
    logger.debug "** current_user = "
    logger.debug current_user.inspect

    user_ids = current_user.timeline_user_ids
--snip--

You may have had some trouble locating the value of current_user in output before, but with human-readable labels, it is easier to spot.

Debugger

Sometimes simply seeing the values of variables after the fact is not enough to debug an issue. The Ruby debugger lets you step into your application as it runs. Inside the debugger, you can see code as it is executed, examine the values of variables, and even change values.

First, edit your application’s Gemfile to add the debugger gem. For Ruby version 2.0 or greater, you should use the byebug gem. Older versions of Ruby should use the debugger gem.

--snip--

# Use debugger
gem 'byebug', group: [:development, :test]

The correct gem for your Ruby version is commented out at the bottom of the Gemfile. Remove the # from the beginning of the line and save the file. The debugger isn’t needed in the production environment, so this line only adds it to the development and test groups.

Because you changed the Gemfile, remember to update installed gems with the bin/bundle install command. You also need to restart the Rails server:

$ bin/rails server

Now that you’ve installed the debugger, let’s see what it can do.

Entering the Debugger

If you call the debugger method in your code, your application stops executing when it reaches that call, and Rails launches the debugger. For example, remove the logger statements you added to the posts index action earlier in app/controllers/posts_controller.rb and instead use the debugger:

class PostsController < ApplicationController
  before_action :authenticate_user!

  def index
    user_ids = current_user.timeline_user_ids

    debugger

    @posts = Post.includes(:user).where(user_id: user_ids)
               .paginate(page: params[:page], per_page: 5)
               .order("created_at DESC")
  end
--snip--

When the index action is called, execution pauses at the debugger statement, and the debugger is started. Refresh the posts index page in your browser. The page shouldn’t finish loading. Check the server output in your terminal, and you should see the debugger prompt:

➊ .../social/app/controllers/posts_controller.rb:9
  @posts = Post.includes(:user).where(user_id: user_ids)

  [4, 13] in .../social/app/controllers/posts_controller.rb
➋    4    def index
     5      user_ids = current_user.timeline_user_ids
     6
     7      debugger
     8
  => 9   @posts = Post.includes(:user).where(user_id: user_ids)
     10                 .paginate(page: params[:page], per_page: 5)
     11                 .order("created_at DESC")
     12    end
     13
➌ (rdb:2)

In the normal server output, you should see a line indicating the current position ➊ in the source code. In this case, execution is paused at line 9 inside app/controllers/posts_controller.rb. Next, the output ➋ shows your place in the code. You should see 10 lines of code with line 9 in the center. Finally, the debugger prompt ➌ is waiting for your input.

Debugger Commands

The debugger accepts a variety of commands for working with your application’s code. This section covers the most common commands. Unless otherwise noted, each of these commands can be abbreviated using the first letter of its name.

Start by entering the help command:

(rdb:2) help
ruby-debug help v1.6.6
Type 'help <command-name>' for help on a specific command

Available commands:
backtrace  break    catch    condition
continue   delete   disable  display
down       edit     enable   eval
exit       finish   frame    help
info       irb      jump     kill
list       method   next     p
pp         ps       putl     quit
reload     restart  save     set
show       skip     source   start
step       thread   tmate    trace
undisplay  up       var      where

(rdb:2)

The help command shows a list of all available debugger commands. You can also follow help with the name of another command for information on a specific command.

When you entered the debugger, you were shown 10 lines of code around your current position. The list command displays the next 10 lines of code inside the debugger.

(rdb:2) list
[14, 18] in /Users/tony/code/social/app/controllers/posts_controller.rb
   14    def show
   15      @post = Post.find(params[:id])
   16      @can_moderate = (current_user == @post.user)
   17    end
   18  end
(rdb:2)

Each time you enter the list command another 10 lines of code are displayed. In this case, the current file has only five more lines of code, so those five lines are shown. Enter list- to see the previous 10 lines of code, and enter list= to show the code around your current position:

(rdb:2) list=
[4, 13] in /Users/tony/code/social/app/controllers/posts_controller.rb
   4    def index
   5      user_ids = current_user.timeline_user_ids
   6
   7      debugger
   8
=> 9   @posts = Post.includes(:user).where(user_id: user_ids)
   10                  .paginate(page: params[:page], per_page: 5)
   11                  .order("created_at DESC")
   12    end
   13
(rdb:2)

Now that you know where you are in the code, you might want to examine the values of some variables. The var command displays currently defined variables and their contents. To see local variables, enter the var local command:

(rdb:2) var local
self = #<PostsController:0x007ffbfeb21018>
user_ids = [2, 1]
(rdb:2)

Here, only two local variables are defined. The variable self indicates that you are inside the PostsController. The variable user_ids received its contents on line 5 in the previous code.

List instance variables and their values with the var instance command:

(rdb:2) var instance
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_env = {"GATEWAY_INTERFACE"=>"CGI/1.1", "P...
@_headers = {"Content-Type"=>"text/html"}
@_lookup_context = #<ActionView::LookupCont...
@_prefixes = ["posts", "application"]
@_request = #<ActionDispatch::Request:0x007...
@_response = #<ActionDispatch::Response:0x0...
@_response_body = nil
@_routes = nil
@_status = 200
@current_user = #<User id: 1, name: "User O...
@marked_for_same_origin_verification = true
(rdb:2)

Quite a few instance variables are already defined at this point. The only instance variable set by this code is @current_user. This instance variable is defined in the current_user method in ApplicationController. The other variables are defined by Rails. Note that @posts is not defined yet. Your current position is line 9, which defines @posts, but that line has not yet been executed.

The display command adds a variable to the display list inside the debugger. If you are especially interested in the value of user_ids, for example, enter the display user_ids command to add it to the display list, as shown here:

(rdb:2) display user_ids
1: user_ids = [2, 1]
(rdb:2)

You can also show the contents of the display list and their values with the display command, abbreviated disp:

(rdb:2) disp
1: user_ids = [2, 1]
(rdb:2)

To remove a variable from the display list, use the undisplay command followed by the number corresponding to a variable in the list. For example, undisplay 1 removes user_ids from the display list.

Use the eval command to evaluate any Ruby code you like and print its value. This command is abbreviated p, as in print. For example, you might want to print the length of the user_ids array or the output from the current_user method.

(rdb:2) eval user_ids.length
2
(rdb:2) p current_user
#<User id: 1, name: "User One", email: "user...
(rdb:2)

The debugger is a Ruby shell, so you can also evaluate Ruby commands by simply entering them at the prompt. The eval command is not even necessary. For example, set the value of user_ids to an empty array by entering this statement at the debugger prompt:

(rdb:2) user_ids = []
[]
(rdb:2)

This prints the return value of the expression user_ids = [] just as if you had typed it in the Rails console.

Several commands are available for executing your application’s code inside the debugger. The most commonly used command is next, which executes the next line of code. The next command executes methods on the next line of code without moving inside the method.

The step command is similar, but it also shows you each line that executes inside method calls. The step command moves through your application and its dependencies literally one line of code at a time. You can use it to find bugs in the Rails framework or other gems used by your application.

When you are finished moving around in your code, use the continue command to resume execution and finish the current request. If you’ve been following along throughout this section, you may remember you set the value of user_ids to an empty array. When you continue execution and the posts index page finally renders, no posts are displayed. Because you set user_ids to an empty array, the @posts instance variable is also empty, and the render @posts statement inside the index view renders nothing.

The Ruby debugger probably isn’t something you’ll use every day, and some developers never use it. But if you ever encounter a really hard-to-find bug, the debugger is invaluable.

Summary

This chapter described several debugging techniques. Displaying values in your application’s views with the debug helper method or adding data to the log file with logger statements will help you track down most bugs. The interactive debugger provides complete control over your application, allowing you to step through your code and pinpoint bugs that are particularly hard to find.

The next chapter covers web application programming interfaces, or APIs. We’ll discuss using other application’s APIs and creating your own.

Exercises

Q:

1. Using the debug helper method, display the contents of each post as it is rendered on the posts index page. Add a debug call inside the partial for each type of post.

Q:

2. Add the id and type of each post in the @posts instance variable to the log using a call to logger.debug in the index action of app/controllers/posts_controller.rb.

Q:

3. Practice using the debugger to explore your application’s code. Use the next command in the debugger to see what happens when a user logs in to the application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.47.166