Rails Debugging Tips

In this article we will use the mood project to learn a few debugging tips.

Where is a given method implemented?

First, let's see how to use source_location to find out where a given method is implemented. We can use method method along with source_location as shown below.

  def create
    @thought = Thought.new(thought_params)

    p method(:respond_to).source_location

    respond_to do |format|
      if @thought.save
        format.html { redirect_to @thought, notice: 'Thought was successfully created.' }
        format.json { render :show, status: :created, location: @thought }
      else
        format.html { render :new }
        format.json { render json: @thought.errors, status: :unprocessable_entity }
      end
    end
  end

Change the application controller to use null_session so that we can create records from the terminal using curl without providing any authenticity token.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end

Start the rails server on port 3001. Use curl in the command line to create a new thought:

$ curl -H "Content-Type: application/json" -X POST -d '{"thought":{"content":"testing debug tip"}, "commit":"Create Thought"}' http://localhost:3001/thoughts

In the log file you will see the file name and line number where the respond_to is implemented in the Rails framework.

["/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_controller/metal/mime_responds.rb", 191]

Where is a given super implemented?

Change the index action as follows:

def index
  # @thoughts = Thought.all
  p method(:render).source_location

  render plain: 'test'
end

In a terminal:

curl http://localhost:3001/thoughts

In the log, you will see the location where the render method is implemented:

["/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_controller/metal/instrumentation.rb", 41]

If you open this file and go to line 41, you will see:

def render(*args)
  render_output = nil
  self.view_runtime = cleanup_view_runtime do
    Benchmark.ms { render_output = super }
  end
  render_output
end

The render_output captures the output of the super call. Where does the super go? What method does it call? Modify the index action to call super_method before we call source_location:

def index
  # @thoughts = Thought.all
  p method(:render).super_method.source_location

  render plain: 'test'
end

In the log file, we can see where the super takes us:

["/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_controller/metal/rendering.rb", 34]

The implementation is shown here:

# Check for double render errors and set the content_type after rendering.
def render(*args) #:nodoc:
  raise ::AbstractController::DoubleRenderError if self.response_body
  super
end

for easy reference. This again calls super. You can ignore the super call in this method. It is a bad practice to call super multiple times. It's an anti-pattern and is called Yo-Yo problem. A programmer has to go through several class definitions to understand the inheritance hierarchy. Ideally, you must not call super more than once to implement a functionality. It is considered a good practice to keep the inheritance hierarchy shallow. Composition is also another tool to consider that can avoid this problem.

What if the Object implements the Ruby built-in method method?

Change the index action and use the previous technique to find the location of headers method:

def index
  p request.method(:headers).source_location

  render plain: 'test'
end

Hit the app:

curl http://localhost:3000/thoughts

You will get the error:

ArgumentError (wrong number of arguments (given 1, expected 0)):

To make this work, change the index:

def index
  method = Kernel.instance_method(:method)
  p method.bind(request).call(:headers).source_location

  render plain: 'test'
end

If you run the curl command, you will now get:

["/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_dispatch/http/request.rb", 189]

You can see the Rails source code showing the headers method implementation:

# Provides access to the request's HTTP headers, for example:
#
#   request.headers["Content-Type"] # => "text/plain"
def headers
  @headers ||= Http::Headers.new(self)
end

We can also see where the object implemented the Ruby built-in method method using the same technique like this:

def index
  method = Kernel.instance_method(:method)
  p method.bind(request).call(:method).source_location

  render plain: 'test'
end

The log file shows the location of the method method:

["/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_dispatch/http/request.rb", 177]

Here is the Rails implementation:

def method
  @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD'))
end

Tracepoint

You can use Tracepoint to trace Ruby code to get a big picture view. Change the index action.

def index
  tp = TracePoint.new(:call) do |x|
    p x
  end
  tp.enable

  render plain: 'test'
ensure
  tp.disable
end   

Run the curl command. This gives ton of output in the log file:

<TracePoint:call `render'@/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/actionpack-5.0.2/lib/action_controller/metal/instrumentation.rb:41>
<TracePoint:call `cleanup_view_runtime'@/Users/bparanj/.rvm/gems/ruby-2.3.3@r5/gems/activerecord-5.0.2/lib/active_record/railties/controller_runtime.rb:21>

100 lines not shown

Reference

I am a puts debuggerer


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Rails Debugging Tips


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.