Vous êtes sur la page 1sur 61

Debugging Ruby

Debugging Ruby code & Rails applications by example

Ryan Bigg
This book is for sale at http://leanpub.com/debuggingruby

This version was published on 2014-10-16

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

2013 - 2014 Ryan Bigg


Tweet This Book!
Please help Ryan Bigg by spreading the word about this book on Twitter!
The suggested hashtag for this book is #debuggingruby.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#debuggingruby
Contents

General Debugging Tips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Debugging frame of mind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Debugging Ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Basic Example #1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Basic Example #2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Basic Example #2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

Debugging Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Workflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Rails Example #1 - form_for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Rails Example #2 - Routing HTTP verbs/methods . . . . . . . . . . . . . . . . . . . . . . . 20
Debugging slow code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
TODO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

Handling Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

Advanced Rails Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32


Advanced Rails Example #1 - Broken Devise Application . . . . . . . . . . . . . . . . . . . 32
CONTENTS i

Thank you for reading Debugging Ruby.


If you find any misteaks while reading this book, please email them to me@ryanbigg.com. If emails
not your thing, I have a review tool that you can use too. Ill need your email address to add you to
that though. Tweet me (@ryanbigg) or email is cool.
If its a problem to do with your code, then please put the code on GitHub and link me to it so I can
clone it and attempt to reproduce the problem myself. If you dont understand something, then its
more likely that Im the idiot and rushed it when I wrote it. Let me know!
If you want to see some code examples and compare them to what you have, you can go view them
over on GitHub. For now, theyre the absolute latest code from the book.
This far into the book and theres already one error. You should expect that it is not alone. It has
friends and their ways are devious. They are coming after your perception of reality. Beware.

Ive been helping out in the #rubyonrails channel on Freenode now for about 7 years and Ive seen a
wide variety of skill levels come through. I also help out frequently on Stack Overflow and my day
job requires doing some of that as well. More often than not though, the visitors of the channel or
people on Stack Overflow are just lacking the basic skills for debugging their code.
The ability to debug code is arguably more important than the ability to write code. Writing code is
easy! Just throw some things together and bash them a couple of times until it works. What could
possibly go wrong? Anything and everything.
Code is not perfect the first time it is written, or even the 10th time it is rewritten. The ability
to debug code written by ourselves, strangers or versions of us from the past that can feel like
strangers sometimes is a valuable ability to have. When things go wrong and they will go wrong
having that ability allows you to dive in without fear into fixing whatever code is causing the fault
quickly and efficiently so you can get back to doing more productive things.
Debugging can be fun because it provides a technical challenge; a puzzle. Deciphering that puzzle
is usually just a matter of walking through the flow of execution within the piece of code which is
breaking, but it is sometimes hard to know where the right place to start is. Once youve found the
place to start, knowing where to go from there can also be difficult. The solution is its own reward
and if youre anything like me you should get a nice little Dopamine hit every time you solve a bug.
Or even one of those from just helping out other people by squashing bugs theyve reported.
Within this book are common examples things that can go wrong in Ruby and Rails code. Ranging
anywhere from a typo in the code, to performance issues and nasty exceptions. This book will cover
a wide gamut of how code can break, and exactly what we can do about it to fix those breakages
and ensure that we know about them as soon as possible.
Lets go!
https://github.com/radar/debug_book_examples
General Debugging Tips
The ability to debug code is arguably more important than the ability to write code. Written code is
sometimes not completely perfect the first time its written, especially when humans are involved in
the writing of that code. Humans make mistakes, and those mistakes can cost time and money. Being
able to fix those mistakes in an efficient manner is a core competency to being a good programmer.
These mistakes are sometimes extremely simple; a typo here, a missing bracket there, defining code
outside of the correct scope. Other times they involve interconnected bits of code and tracking down
the misbehaving cog (or cogs) in that wonderful machine can take some time.
In this book, were going to go through some common coding mistakes that people make and we
will look at how to solve them. The deeper and deeper you get into this book, the more complicated
the examples will get. All the examples in this book will be written in Ruby.
Before we get to see any code, lets talk about getting in the right frame of mind for debugging.

Debugging frame of mind


Have you ever come across a bug thats been too hard to fix, only to have the answer come to you
after you distract yourself from the problem for an extended period of time? I most certainly have.
I think that just by sitting there for a bit longer that I can magically know the answer. Most of the
time that this is not true. There are many other ways to solve the problem, but I focus on my current
line of thinking and get caught up in a frustrating loop. The loop is thinking that I can solve the
problem, attempting to solve it and then not being able to solve it.
Ive solved these kinds of problems by sleeping, having lunch, exercising or by talking people in the
community and asking them about the problem. These are all common sense, but its worth covering
them even if it is for a short while.
Sleep has the best effect on my ability to debug. A good nights sleep is easy to come by as Im a newly
married man who lives in a first-world country and Im thankful for all of those things. Sleeping
well makes me less grumpy, more rational in my thinking and generally improves my mood and
outlook on things. Debugging on low sleep leads only to frustration.
Similarly, eating (and eating healithly) also helps. Having to debug something when my stomach is
rumbling is hard work. Staying the course and attempting to fix the problem is not the best course
of action, but I still do it anyway. Grabbing something to eat helps a lot with that. If its some lollies,
then the initial sugar hit boosts me up, but then comes the inevitable crash. Eating healthily improves
my long-term ability to debug well, and so I try to do that as often as I can.
Hence the books subtitle How to find the answer to your problems in a sandwich. Realistically for me though it would be an Indian curry,
Thai dish, salad, burger or yiros. Sandwich just has a better ring to it than all those other things.
General Debugging Tips 2

Exercising has been scientifically proven to enhance brain power (and general happiness!), and
along with that comes the ability to reason better about code. Going for a walk, riding a bike or a
session at the gym are all ways that I think have boosted my ability to debug code.
Lastly, talking to other people is absolutely worthwhile. Ive found the answer to many a problem
by just simply explaining whats going wrong to someone else. This technique is called the Rubber
Ducky Technique. You talk to the person, they say nothing (or very little), and suddenly an epiphany
happens. The answer is known. Its a funny thing.
If the answer doesnt come by way of explanation, other people have their own brains to reason
about things with and their own takes on things. The way that you debug something might be
different to the way that they debug it. Getting someone else to look over the code and work with
me in reasoning about a problem is, by far, the most productive debugging technique that I know
of.
So in summary: make sure youve caught up on your sleep, have eaten well, have exercised well,
and dont be afraid to speak to other people in the Ruby community about any issues you have.
Lets now dive into some practical examples of debugging in Ruby.
http://www.psychologytoday.com/blog/the-athletes-way/201401/what-is-the-best-way-improve-your-brain-power-life
http://www.apa.org/gradpsych/2013/09/exercise.aspx
http://www.nytimes.com/2012/04/22/magazine/how-exercise-could-lead-to-a-better-brain.html?pagewanted=all
Debugging Ruby
In this chapter well cover some basic Ruby code that has some small problems with it, and figure
out how to fix it so that the code works once again.

Basic Example #1
Our first problem we can cause ourselves in the irb console. Lets start this up now by using irb
and put this in it:

1 "test".join

When we type this and hit enter, well see a NoMethodError exception:

1 irb(main):001:0> "test".join
2 NoMethodError: undefined method `join' for "test":String
3 from (irb):1
4 from .../ruby-2.1.3/bin/irb:11:in `<main>'

This exception shows us that something exceptional happened with our code namely a NoMethodError.
The text after NoMethodError shows more detail about what happened, and then the lines under-
neath it show us how the script got to that point, starting from the bottom up, and thats called a
stack trace.
Why are we getting this error? Well, the "test" object is an object of type String, as is indicated on
the first line of our exception output with the words "test":String. If we look at the documentation
for Rubys String class, we can see that there is indeed no method called join.
This error is happening because were calling a method that doesnt exist. In this example weve
seen what a stack trace is and how to read it. The example is slightly contrived, but nevertheless
demonstrates how we can find whats happening.
Lets take a look at a proper Ruby program which has another, similar bug and talk more about stack
traces.

Basic Example #2
Lets take a look at our first very simple Ruby script with an error:
http://www.ruby-doc.org/core-2.1.3/String.html
Debugging Ruby 4

class Car
attr_accesssor :on
end

This is some fairly simple Ruby code. All were doing is defining a class called Car and then calling
the attr_accesssor method inside that class which should define a setter method called on= where
we can store a value, and a getter method called on where we can retrieve that value. We dont
necessarily care about the setting and getting just yet. All we care about is that our code works.
Put this code into a new file called car.rb and try running it with ruby car.rb. Ideally, nothing
should be output to the terminal, because we have not told Ruby to output anything. Instead of
nothing, you should see this:

car.rb:2:in `<class:Car>':
undefined method `attr_accesssor' for Car:Class (NoMethodError)
from car.rb:1:in `<main>'

The output here is modified slightly to fit into the book. The first two lines in the above output
should be one line. This is another stack trace, just like the one we saw in the earlier example. These
are output by any Ruby program that encounters an exceptional circumstance and it shows us the
execution flow of the program in reverse order. Lets start from the final line of the stacktrace:

from car.rb:1:in `<main>'

This line tells us that the execution flow of the program started on line 1 of the car.rb file. The
<main> here indicates the main namespace that all code within Ruby operates in. This line of the
stacktrace is not too important. All its indicating is that on line 1 of our program, something is
executing and altering what our program is going to do.
The first line of the stacktrace begins with this:

car.rb:2:in `<class:Car>'

This line tells us that on line 2 of car.rb, were operating within the scope of a class called Car
(<class:Car>). Because this is the top line of the stacktrace, this is probably where our error has
occurred. In most cases, the top line of the stacktrace will point to the exact line where an error
occurs.
The remainder of this line is this:

undefined method `attr_accesssor' for Car:Class (NoMethodError)


Debugging Ruby 5

This part of it tells us the error thats happened. In this case, were trying to call a method that Ruby
has no knowledge of, the attr_accesssor method. Were trying to call that method on the Car class,
but that method does not exist there. When we try to call a method that Ruby does not know about,
it raises a NoMethodError exception, which is shown in parentheses at the end of the line.
Our program is 3 lines long, but the third line is just an end, and those are never included in
stacktraces because theyre just there to demarcate the end of a scope within the code, rather than
perform a function.
From this stacktrace, we can gather that the error is happening on line 2. All were doing on line 2
is this:

attr_accesssor :on

Do you see the mistake weve made yet? It might be glaringly obvious or it might not be. Look at it
for a moment longer. Do you see it now?
The mistake weve made is dead simple: weve typed three ses rather than two. Weve done attr_-
accesssor, rather than attr_accessor. The code should be this:

attr_accessor :on

Change the code on line 2 to that now and re-run the program. It should run without showing
anything. This is the standard for Ruby programs when they execute successfully. Unless we have
specifically told it to output anything i.e. with a call to the puts method nothing will happen.

Verify program success


You can verify that a program has run successfully by running this command (assuming
youre using Bash or a similar shell):

1 echo $?

If that outputs 0, then everything has worked correctly.


TODO: Bash error codes mention goes here.

Now that weve fixed that error, lets cover another.

Basic Example #2
Look at this code:
Debugging Ruby 6

class Car
attr_accessor :on
def start
on = true
end
end
car = Car.new
car.start
puts car.on

Can you tell what its doing just by looking at it? It defines a new class called Car, and a virtual
attribute inside that class by way of attr_accessor called on. This time, attr_accessor is spelled
correctly. Remember that the attr_accessor call defines a getter method called on and a setter
method called on=.
After the attr_accessor, the code defines a method called start which sets on to true. It then ends
the method and class definition.
On the final two lines, the code creates a new instance of this class with Car.new and assigns it to
a local variable, calls start which should do what its told, and finally outputs the value of car.on
using puts. Just by looking at this code, you may expect that when it is run that it will output true.
Youd be wrong.
Put this code into car.rb and try running it. This is what will happen:

$ ruby car.rb
$

Rather than nothing being output this time, a blank line has been output. This is because the program
has done what weve told it to do. It does not output true, even though the start method is setting
on to true. This time we dont have a stacktrace to tell us what line the error is probably on. We
have to step through the programs execution ourselves.
Since there are no exceptions raised within the code, we can assume that the code itself is valid.
Lets step through the final three lines of the code:

car = Car.new
car.start
puts car.on

The first line here creates a new instance of the Car class. Thats Rubys code, so thats probably not
the source of our frustration. The second line calls the start method which sets on to true. The final
line just outputs the value of on, which isnt too special a task. The problem probably lies within
our second line. Lets look at how that method is defined:
Debugging Ruby 7

def start
on = true
end

This code inside the method is not dissimilar to the first line of the previous example:

car = Car.new

When we call car = Car.new were setting a local variable to the value of whatever Car.new returns.
In the start method, were setting an on local variable to true. This is not our intention! Our
intention is to set the virtual attribute for the instance to true. Beware this very fine difference!
Theres more than one way to set this virtual attribute. We can go through the setter method call,
like this:

def start
self.on = true
end

The caveat of this is that the on= method may define other behaviour. This is a rare occurrence in
Ruby code, but it can happen. In this case, its obvious that the on= method isnt redefined to add
extra behaviour, so its OK to do this. Another way of doing it would be to set the instance variable
of the same name, like this:

def start
@on = true
end

Doing it this way has the benefit of the code being shorter and there being no unintended side-
effects with the on= method. Using either the setter method or setting the instance variable are both
legitimate ways of solving the issue with our code. Go ahead and try both ways now. They should
both cause the program to output this when its run:

$ ruby car.rb
true

Great, so weve solved this problem. The issue here was that we were trying to set a local variable
inside the method, rather than referencing the virtual attribute setter method or instance variable.
With that out of the way, lets look at the possibility of a setter method behaving badly as we talked
about before. Lets change our code to this:
Debugging Ruby 8

class Car
attr_accessor :on
def start
self.on = true
end
def on=(value)
@on = !value
end
end
car = Car.new
car.start
puts car.on

The start method is now using the setter method, on=, to set the value of the virtual attribute called
on. The setter method is overriden from the default here directly underneath that. It takes the value
and negates it, and then stores it on that @on instance variable. This means that when we call on,
well get the opposite of what it should be: setting it to true will make it false and vice versa.
Try running this code yourself now and youll see:

$ ruby car.rb
false

The on= method is maliciously negating our value, giving us an unexpected outcome. If we set the
instance variable within the start method directly, this would bypass the on= method and we would
see the right value. The start method would look like this:

def start
@on = true
end

And the output of the program would look like this:

$ ruby car.rb
true

Its extremely rare that a setter method will perform strange functions like this, but it still is
worthwhile knowing that it can happen, just in case we come across a situation like this in our
debugging experiences.
Debugging Rails
Rails applications are generally more complex than one or two files. More often than not, youll
have many different pieces of the application all working together: the routes, the controllers, the
models, the views and the helpers. In this chapter, well cover some examples of errors that people
make within Rails applications and how to fix them.

Workflow
Each section below works through one particular problem in a Rails application. These Rails
applications are kept on GitHub at https://github.co m/radar/debugging_book_examples and are
numbered the same way as their examples; i.e. Example #1s code is within the directory called 1
underneath the rails directory.
To work through the examples of these Rails applications, youll need to clone that repository to
your computer:

git clone git://github.com/radar/debugging_book_examples

Each Rails application comes fitted with RSpec and Capybara tests which will pass once the code
has been fixed. We can run these tests to execute some code which wil check if the application
is working. Running automated tests is just an easier way compared with manually viewing the
application ourselves and saves time in the long run as our applications get more and more complex.
Lets begin with the first example now.

Rails Example #1 - form_for


In this example well cover:

The debug helper


How to read log output
How constant lookup in Ruby works

In this first application example, we have a very basic Rails application. The thing that we want this
application to do right now is for the users to be able to go to the New Post page without any
issues, but unfortunately theres a problem thats preventing them from doing that which well see
shortly.
Before we do anything with this application, lets install all the dependencies:
https://github.com/radar/debugging_book_examples
Debugging Rails 10

bundle install

Now lets look at the automated test we have inside spec/features/posts_spec.rb inside the
application, written using RSpec and Capybaras DSL:

require 'spec_helper'
feature "Posts" do
it "can begin to create a new post" do
visit root_path
click_link 'New Post'
find_field("Title")
find_field("Body")
end
end

This test will pass if it can navigate to the root page, click New Post and see two fields, one for
Title and one for Body. If we run the test now, well see this error:

1) Posts can begin to create a new post


Failure/Error: click_link 'New Post'
ActionView::Template::Error:
First argument in form cannot contain nil or be empty
# ./app/views/posts/new.html.erb:1:in `...'
# ./spec/features/posts_spec.rb:6:in `block (2 levels) in <top (required)>'

This is a typical failure output from RSpec, shown when code raises an exception. The first line
of the message shows us which test is failing. We should have a pretty good idea which test that
is out of the one test that we have so far. The particular exception that were seeing with this test
is ActionView::Template::Error, and the message for that exception is First argument in form
cannot contain nil or be empty. Well get to that in a moment.
The two remaining lines in this output show some of the stacktrace for the error. It shows us that
posts_spec.rb line 6 does something that triggers some code in app/views/posts/new.html.erb
to run, and thats where the error is (probably) occurring. According to the stacktrace, thats line 1
of app/views/posts/new.html.erb.
All this application has inside it is a PostsController which does nothing other than inherit from
ApplicationController. This controller is routed to by the two routes within config/routes.rb:
Debugging Rails 11

Bug::Application.routes.draw do
root "posts#index"
resources :posts
end

We have a view at app/views/posts/index.html.erb which just contains a link to the new action:

<%= link_to "New Post", new_post_path %>

When we go to that new_post_path, it will render app/views/posts/new.html.erb which contains


this content:

<h2>Writing a new post</h2>


<%= form_for @post do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<%= f.submit %>
<% end %>

So thats all we have: some routes, a controller that does nothing special, a template at app/views/posts/index.html.
and another at app/views/posts/new.html.erb. Just four interconnected pieces in this application,
nothing too special.
Lets jump back to our test now and see what it said when we ran it with bundle exec rspec
spec/features/posts_spec.rb.

1) Posts can begin to create a new post


Failure/Error: click_link 'New Post'
ActionView::Template::Error:
First argument in form cannot contain nil or be empty
# ./app/views/posts/new.html.erb:1:in `...'
# ./spec/features/posts_spec.rb:6:in `block (2 levels) in <top (required)>'

The first line of the error is pointing directly to the first line in app/views/posts/new.html.erb:
Debugging Rails 12

<%= form_for @post do |f| %>

All were doing on this line is calling the form_for method and passing it the @post instance variable
and then starting a block. Later on in that file, were calling some methods on the block argument,
but that doesnt really matter. The error is fair-and-square happening on this line. The most useful
part of the error message is the message for the exception:

First argument in form cannot contain nil or be empty

The argument that its talking about here is the @post variable that were passing. Its claiming that
the variable is nil or empty, and it probably is.

The debug helper


We can see for ourselves if this variable is actually nil by removing all the code in app/views/posts/new.html.erb
and replacing it with this single line:

<%= debug @post.inspect %>

The debug method here is provided by Rails and will output a YAMLized version of whatever it is
passed. Were passing to it the output of the @post.inspect call. The inspect method is a method
provided by Ruby which outputs a human-friendly version of the object. If youve ever written Ruby
in irb, youve seen the inspected versions of objects perhaps without even realising it.
Start a new irb session now and try entering these things:

1
1.inspect
puts 1.inspect
"1"
"1".inspect
puts "1".inspect
[1,2,3]
[1,2,3].inspect
puts [1,2,3].inspect
nil
nil.inspect
puts nil.inspect
Debugging Rails 13

Well see that the non-inspect versions are almost identical to the inspect versions. The inspect
versions just have more quotes around them. This is because the inspect call always returns a
String object, as those are easiest for humans to read. Whatever the final thing is on the line for
some code entered into IRB is what will be returned in the IRB prompt. In the case of the puts
calls, IRB returns nil because puts returns nil. IRB automatically uses inspect to return a human-
friendly representation of whatever is entered into the prompt.
The debug method in our view will not automatically call inspect on whatever it is passed. Instead,
it calls another method: to_s. In some cases this method gives back similar output to inspect. Try
the above examples with to_s rather than inspect and see what happens.
For everything including nil we see a string representation of that object. For the number 1 we see
"1". For the string "1", we see the object again because its already a string. Converting an Array
to a String gives us "[1,2,3]", which clearly shows us that the object is an array consisting of the
elements 1, 2 and 3.
Calling nil on the other hand, produces nothing; just an empty string (). Thats because nil is
nothing. This whole explanation was to demonstrate that calling this code is not enough:

<%= debug @post %>

Due to the debug method calling to_s rather than inspect on the objects it is passed. We must call
inspect ourselves:

<%= debug @post.inspect %>

This way, the debug method receives a String object already and will just output that.
Now lets see this in action by firing up a new server process with this application:

rails server

Navigating to http://localhost:3000 will show just the New Post button. We know that the root
action works because our test is not failing on that line. Clicking New Post now will show us the
output of the debug call, which will be this:

--- nil
...

We can see here that our @post variable is indeed nil, just like the error message said: First
argument in form cannot contain nil or be empty. Now why is this?
We know that the route is working correctly because were currently on the page at /posts/new
looking at the debug message we put there. The route routes to the controller, and the controller is
Debugging Rails 14

empty. After the controller runs, the view template is rendered. Nowhere along the chain is the @post
variable defined for our form_for call in app/views/posts/new.html.erb and that is the cause of
this bug.
Where should we be defining this variable? Not in the view itself, because it is never the
responsibility of the view to collect data. That is the controllers job, and so the code to define
the @post variable should go in the controller. But where in the controller?
A clue lies in the server output over in our console.

Reading log output


When we made a request to /posts/new, it shows this:

Started GET "/posts/new" for 127.0.0.1 at 2013-11-09 11:29:09 +1100


Processing by PostsController#new as HTML
Rendered posts/new.html.erb within layouts/application (0.6ms)
Completed 200 OK in 4ms (Views: 3.6ms | ActiveRecord: 0.0ms)

This text has a whole lot of information compressed into a little bit of space. Knowing how to read
and interpret logs from Rails is an important skill, so lets go through the details of this now. On the
first line we have the details about the request:

Started GET "/posts/new" for 127.0.0.1 at 2013-11-09 11:29:09 +1100

This shows us that we have made a GET request to the application, requesting the /posts/new path.
The next two bits of information is the IP address of our local computer and the timestamp for the
request.
On the second line we have this:

Processing by PostsController#new as HTML

This indicates to us that the route has been matched by Rails and has routed to the PostsControllers
new action. The request is a standard HTML format, meaning that HTML output will be returned
by this request.
The third line is this:

Rendered posts/new.html.erb within layouts/application (0.6ms)

This tells us that the posts/new.html.erb template was rendered within layouts/application.
This means that the controller has automatically chosen to display this template for this action, and
has used the default layout for the application to wrap around that template. All the rendering took
0.6ms in this case.
The fourth and final line shows this:
Debugging Rails 15

Completed 200 OK in 4ms (Views: 3.6ms | ActiveRecord: 0.0ms)

This line tells us that the response was completed successfully and returned a 200 OK response. This
is the HTTP status part of the response which indicates to browsers the final status of their request.
We can see that the request completed in 4ms total, with the views taking 3.6ms of that time and
ActiveRecord taking no time at all. The remaining 0.4ms were taken up by unknown things.

Logs are written to files


If you want to go back and see what happened after youve shut down the server, well be
able to do that by viewing logs/development.log, which stores the exact same data that
is displayed when the server is running. The logs directory is where Rails writes its log
data to, and the filename is simply the environment that the Rails application is running
within. By default, that environment is development, so the log file that will be written
to will be logs/development.log.

We know that we need to define the @post variable within the controller to make it available to the
view, but where exactly? The logs tell us where exactly:

Processing by PostsController#new as HTML

This line from the logs is telling us that the new action within the PostsController is being run
before the view is rendered. This would be a perfect place to set up the variable, so lets do that now
by defining this code:

app/controllers/posts_controller.rb

1 class PostsController < ApplicationController


2 def new
3 @post = Post.new
4 end
5 end

Is this enough to fix our test? Lets find out by running bundle exec rspec spec/features/posts_-
spec.rb.
Debugging Rails 16

1) Posts can begin to create a new post


Failure/Error: click_link 'New Post'
NameError:
uninitialized constant PostsController::Post
# ./app/controllers/posts_controller.rb:3:in `new'
# ./spec/features/posts_spec.rb:6:in `block (2 levels) in <top (required)>'

Not quite! Were now seeing a new error which is a NameError exception. This time its happening
from line 3 of the PostsController, which is this line:

@post = Post.new

The error says uninitialized constant PostsController::Post, but on this line were not looking up
the PostsController::Post constant, we just want Post! So whats happening here? Why does it
say PostsController::Post and not just Post?

Constant lookups in Ruby


When Ruby attempts to look up a constant, it will first attempt to look it up within the current
constant context. Because were within the PostsController, it will attempt to look it up there. It
will then travel up the hierarchy looking for that constant until it reaches the top-level namespace.
If it cant find it there, then it gives up and shows an error message saying that it couldnt find it in
the current context. We can demonstrate this constant lookup using a very basic Ruby program:

FOO = "foo"
module Foo
class Putter
def self.put
puts FOO
end
end
end
Foo::Putter.put

With this code, weve defined a FOO constant at the very top level. After that, weve defined a module
called Foo and a class called Putter, and that class has a method called put which calls puts FOO.
This code will search up the hierarchy, looking for the constant in the Putter class, then the Foo
module and then finally the main namespace.
Go ahead and put this code in a new file and try to run it. Well see it outputs foo. The constant
lookup is working correctly.
Now comment out the FOO constant and try running it again. Well see this happen:
Debugging Rails 17

foo-putter.rb:5:in `put': uninitialized constant Foo::Putter::FOO (NameError)


from foo-putter.rb:10:in `<main>'

Ruby is telling us here that it cannot find the constant any more, which is true because we
commented it out! The most important part of this error message is that it cant find the constant
within the Foo::Putter namespace.
Try now uncommenting FOO and moving the constant to inside the module Foo, like this:

module Foo
FOO = "foo"
class Putter
def self.put
puts FOO
end
end
end
Foo::Putter.put

When we run this code again, well see that it works just the same as if the constant was defined in
the main namespace. The code will work also if the FOO constant is inside the class:

module Foo
class Putter
FOO = "foo"
def self.put
puts FOO
end
end
end
Foo::Putter.put

This should demonstrate quite well how constant lookup works within Ruby. Lets go back to solving
our new problem, the uninitialized constant PostsController::Post message, armed now with our
new knowledge of constant lookup.

Creating the Post model


We need to define a Post constant within the application for our test to be happy. The best way to
do this would be to generate a new Post model, which we can do with this command:
Debugging Rails 18

rails g model post

Along with this model comes a migration to create the table. If we attempt to view our application
without running this migration, well see this error when we make a request to http://localhost:3000.

Migrations are pending;


run 'bin/rake db:migrate RAILS_ENV=development' to resolve this issue.

We can fix this by running the command that it tells us to:

bin/rake db:migrate RAILS_ENV=development

Well see a similar error when we try to run our automated test, bundle exec rspec spec/features/posts_-
spec.rb:

... Migrations are pending;


run 'bin/rake db:migrate RAILS_ENV=test' to resolve this issue.

We can run the recommended command here as well to fix the problem.

bin/rake db:migrate RAILS_ENV=test

When we run our test again, well see a different error:

Failure/Error: click_link 'New Post'


ActionView::Template::Error:
undefined method `title' for #<Post id: nil, created_at: nil, updated_at: nil>
# ./app/views/posts/new.html.erb:4:...
# ./app/views/posts/new.html.erb:1:...
# ./spec/features/posts_spec.rb:6:...

Weve now gotten past the error in our controller and now were back to an error within
app/views/posts/new.html.erb. While line 1 of this file is mentioned in the stacktrace, it is not
the final line and therefore the error is probably not occurring on that line. The very first line of the
stacktrace points to line 4, which is this line:
Debugging Rails 19

<%= f.text_field :title %>

The error were seeing is happening because somehow title is being called on an instance of the
Post class. This is happening because the text_field helper, along with many other form helpers
in Rails, will attempt to populate the form with the value from the attribute. It does this by trying to
call the attributes method. If the attributes method is not there, then we see this error happening.
Whats happening here is that we didnt define any columns in the posts table, which means that
there will be no title or body attribute defined for the form to use.
We can fix this by altering the migration for the posts table, which is the only migration in the
db/migrate folder. Before we alter it, we need to undo the migration, which we can do by running
this command:

bin/rake db:rollback

Now the database is back to its pristine state, we can alter that migration:

class CreatePosts < ActiveRecord::Migration


def change
create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end
end
end

Now that the migration is correct, we can run this command to create the table with the correct
columns:

bin/rake db:migrate

We need to do the same thing with the test database, and that can be done with this command:

bin/rake db:test:prepare

Lets run that test again and see if everythings running smoothly:

1 example, 0 failures

Yay! Our test is now passing. The router is receiving the request and passing it to the PostsController.
The new action in that controller is defining a @post instance variable set to a new instance of the
Post model. The app/views/posts/new.html.erb template is being run and rendering the form.
The form is attempting to fetch the attributes from the new Post model instance, but since there are
none then the fields will be left blank. All the parts are working in unity and weve debugged this
bug.
Debugging Rails 20

Rails Example #2 - Routing HTTP verbs/methods


In this example well cover:

Debugging routing issues


The rake routes command

This example is very similar to the first example. The changes are that our tests in spec/features/posts_-
spec.rb have now changed to this:

require 'spec_helper'
feature "Posts" do
it "can create a new post" do
visit "/"
click_link "New Post"
fill_in "Title", :with => "Hello world"
fill_in "Body", :with => "This is the first post."
click_button "Create Post"
page.should have_content("Created post.")
end
it "can update an existing post" do
Post.create({
:title => "Hello world",
:body => "This is the first post."
})
visit "/"
click_link "Hello world"
fill_in "Title", :with => "Hello World"
click_button "Update Post"
page.should have_content("Updated post.")
end
end

When we run the tests using bundle exec rspec spec/features/posts_spec.rb, well see this
error:
Debugging Rails 21

1) Posts can update an existing post


Failure/Error: click_button "Update Post"
ActionController::RoutingError:
No route matches [POST] "/posts/1"
# ./spec/features/posts_spec.rb:21 ...

Lets mull over this error while we look through the code in the application.
Rather than having a test to ensure that we can see the new post form correctly, the first test now goes
through all the motions of creating a new post. It does this by going to the root path of the application
and then clicking New Post. This routes to the new action within the PostsController:

def new
@post = Post.new
end

This then renders the app/views/posts/new.html.erb view which has changed since the last time
we saw it:

<h2>Writing a new post</h2>


<%= render "form" %>

The form has been moved into a partial so that it can be used within the edit actions template as
well. The partial is located at app/views/posts/_form.html.erb and contains this content:

<%= form_for @post, :html => { :method => :post } do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<%= f.submit :class => "btn btn-primary" %>
<% end %>

For the new action, this form will send a request to the create action. For the edit action, we can
expect it to send the data to the update action instead because the record represented by @post is
persisted within the database.
When this form is submitted from the new template, it will go to the create action within
PostsController:
Debugging Rails 22

def create
@post = Post.create(post_params)
flash[:notice] = "Created post."
redirect_to root_path
end

This simply creates the post from the data posted in and sets a flash notice. We know that all of this
is working because the first test within spec/features/posts_spec.rb is working. Its the second
test which is failing. Lets look at that failing test again:

it "can update an existing post" do


post = Post.create({
:title => "Hello world",
:body => "This is the first post."
})
visit "/"
click_link post.title
fill_in "Title", :with => "Hello World"
click_button "Update Post"
page.should have_content("Updated post.")
end

This test is failing like this:

1) Posts can update an existing post


Failure/Error: click_button "Update Post"
ActionController::RoutingError:
No route matches [POST] "/posts/1"
# ./spec/features/posts_spec.rb:21 ...

This test first creates a new Post instance. It then visits the root path, which is a listing of posts. The
index action within PostsController is responsible for collecting all the posts:

def index
@posts = Post.all
end

The template at app/views/posts/index.html.erb renders all these posts within a table, showing
a link with the posts title for each post:
Debugging Rails 23

<h2>Posts</h2>
<%= link_to "New Post", new_post_path, :class => "btn btn-primary" %>
<table class='table'>
<thead>
<th>Title</th>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= link_to post.title, edit_post_path(post) %></td>
</tr>
<% end %>
</tbody>
</table>

The test then clicks on the newly created posts title which takes us to the edit action with
PostsController:

def edit
@post = Post.find(params[:id])
end

This action renders the template at app/views/posts/edit.html.erb:

<h2>Editing <%= @post.title %></h2>


<%= render "form" %>

This renders the same form partial that we saw earlier used by the template in app/views/posts/new.html.erb:

<%= form_for @post, :html => { :method => :post } do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<%= f.submit :class => "btn btn-primary" %>
<% end %>

After the edit template has been rendered, the test edits the title of the post to capitalize the word
world within the title. It then clicks Update Post and thats where our test is failing. The main
part of the error, one more time:
Debugging Rails 24

ActionController::RoutingError:
No route matches [POST] "/posts/1"
# ./spec/features/posts_spec.rb:21 ...

The stacktrace that follows this isnt really that helpful because it only shows us the line in the spec
which is triggering this problem and doesnt point to the error in our code so far. We could get a
bigger stacktrace by running this command:

bundle exec rspec spec/features/posts_spec.rb -b

But this shows us a lot of Rails and Capybara internal code, and its more likely that our code is at
fault than Railss or Capybaras. This time, were not being pointed in any particular direction and
so we need to walk through the steps by ourselves to figure this out.
We know in the test that were about to navigate to the root page, and then from there navigate to the
edit page and fill in the Title field. Its the clicking the Update Post button that is failing, claiming
that there is no route defined. We can check the routes defined for the application by running the
rake routes command which will show us this:

Prefix Verb URI Pattern Controller#Action


root GET / posts#index
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy

This output shows us all routes which are defined within our small application. We want a route
that goes to the update action. There are two of these within the rake routes output:

PATCH /posts/:id(.:format) posts#update


PUT /posts/:id(.:format) posts#update

When making a request within Rails, we need to be careful that we use the correct HTTP
verb/method. The routes defined for the update action use either a PATCH or a PUT request. From
the feedback in our test, we can quite clearly see were not doing that:
Debugging Rails 25

ActionController::RoutingError:
No route matches [POST] "/posts/1"
# ./spec/features/posts_spec.rb:21 ...

Were using POST rather than PATCH or PUT. Since we know that we can get to the edit action template
just fine, lets start our debugging from there. What were looking for is somewhere that is telling
the code to make a POST request rather than a PUT or PATCH. The app/views/posts/edit.html.erb
template contains this code:

<h2>Editing <%= @post.title %></h2>


<%= render "form" %>

This is fairly standard and nothing is standing out here. Lets look at the partial within app/views/posts/_-
form.html.erb:

<%= form_for @post, :html => { :method => :post } do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<%= f.submit :class => "btn btn-primary" %>
<% end %>

On the very first line here is where were explictly telling the code to use the POST verb rather than
PUT or PATCH. If we change this to use put or patch, like this:

<%= form_for @post, :html => { :method => :put } do |f| %>

Or:

<%= form_for @post, :html => { :method => :patch } do |f| %>

Then run our tests with bundle exec rspec spec/features/posts_spec.rb, well see our first test
is now failing with a similar reason:
Debugging Rails 26

1) Posts can create a new post


Failure/Error: click_button "Create Post"
ActionController::RoutingError:
No route matches [PATCH] "/posts"
# ./spec/features/posts_spec.rb:9 ...

Doomed if you do, doomed if you dont. What were failing to realise here at this point is that
form_for automatically takes care of using the right HTTP verb/method. We dont actually need
to specify the method. Lets remove that option from the form_for now, leaving that first line like
this:

<%= form_for @post do |f| %>

When we run our tests again, they will now pass:

2 examples, 0 failures

The issue all along was that we were explicitly specifying the HTTP verb/method for the form, but
we didnt need to do that because Rails deals with this automatically. Rails does this by checking
the persisted? methods return value. This method returns true if the object is represented in the
database with a record in the table, or false if not. Lets go into the rails console now and see
what this does.
When we try that method with a new Post object, well get this:

irb(main):001:0> post = Post.new


=> #<Post id: nil, title: nil, body: nil, created_at: nil, updated_at: nil>
irb(main):002:0> post.persisted?
=> false

If we try it with a Post object thats fetched from the database, well get this:

irb(main):001:0> post = Post.create


=> #<Post id: 1, title: nil, body: nil, created_at: nil, updated_at: nil>
irb(main):002:0> post.persisted?
=> true

If the return value of this persisted? method is false, the form_for helper will route to the plural
name of the resource using posts_path in this case. It will also use the POST HTTP verb/method. If
the return value of the method is true, then it will route to the singular name of the resource using
post_path(post), using the PATCH HTTP verb/method.
Lets leave the HTTP verb/method deciding up to form_for, unless we absolutely know better.
Debugging Rails 27

Debugging slow code


Without proper care, applications can get slow over time. Pages that once loaded quickly can degrade
into taking more than a second to load. This can be caused by a slow database lookup, too many
database lookups or just even too much information displayed on the page at any particular time.
Slowness within an application can almost be called a bug itself. Its definitely not a feature!
In this chapter, well cover some common pitfalls that people can inadvertently stumble upon during
Rails application development. You might even be surprised at how easy some of the fixes are.

Rails Example #4 - Reducing Repeated Queries


In this section well cover:

Includes queries
Pagination

This example app is a slight modifications on the earlier blogging application. In this application, we
have a Post model which has_many :comments, and that Comment model has_many :users. Before
we start this application, were going to need to run this command to set up some test data:

rake db:seed

All this task will do is run the code within the db/seeds.rb file within the context of our Rails
applications development environment. This code looks like this:

post = Post.create(:title => "Hello World", :body => "This is the first post.")
users = 5.times.map do
User.create(:name => "User#{rand(9999)}")
end
1000.times do
Comment.create(
:post => post,
:user => users[rand(5)],
:body => "This is just a comment."
)
end

This code creates a single post, then 5 randomly numbered users, then 1000 comments with the same
post and text, but a randomly chosen user for each comment. Lets look at what this data makes our
application do by firing up the Rails server:
Debugging Rails 28

rails s

Going to http://localhost:3000 will show us the familiar list of posts:

Posts

Clicking on this posts link will now show us the post itself, along with a whole bunch of comments:

Posts

Your numbers will probably be different, but thats alright. If we switch over to the terminal session
where rails s is running, well see a quite large amount of queries that are being run to load our
data:

Processing by PostsController#show as HTML


Parameters: {"id"=>"1"}
Post Load (0.1ms) SELECT "posts".* FROM "posts" ...
Comment Load (2.8ms) SELECT "comments".* FROM "comments" ...
User Load (0.2ms) SELECT "users".* FROM "users" ...
CACHE (0.0ms) SELECT "users".* FROM "users" ...
User Load (0.2ms) SELECT "users".* FROM "users" ...
CACHE (0.0ms) SELECT "users".* FROM "users" ...

At the top of this mountain of queries is the query to load the post. This needs to happen, otherwise
we would not be able to show the posts data. The second query is one to load the comments, which
runs for a similar reason: we need to show the comments. The 1000 queries (dont count them) that
follow are loading all the users for the comments. But why are there 1000 queries when theres
only 5 users to load? Because Rails doesnt know any better. Luckily for us though, Active Record
is caching the results of these queries and fetching the back from the cache when it goes to run that
query again. These lines in the output begin with CACHE.
At the end of the output, well see this:

Completed 200 OK in 681ms (Views: 655.3ms | ActiveRecord: 18.4ms)

The entire action is taking 681ms to render, 655ms of that is within the view and 18.4ms is happening
within Active Record. A lot of this slowness is due to the number of queries and query cache hits
that Rails is undergoing during this request.
Interesting trivia fact: The probability of the numbers not being different is 1 in 10000 to the power of 5, or 1 in 100 quintrillion (1 in
100,000,000,000,000,000,000).
Debugging Rails 29

When any action within our application runs, we should try and minimize the database querying
needed for that action. Even a single query takes time, and so ideally we would like to reduce it down
to 0 queries. Lets not go all the way to that extreme yet. We should focus our efforts on making
Active Record stop performing 1000 lookups for these users.
The way we can fix this problem is with something in Active Record called eager loading. Eager
loading will load the required data in as few queries as possible. We can use this within the show
action of the PostsController like this:

def show
@post = Post.includes(comments: :user).find(params[:id])
end

The includes statement here triggers eager loading for this query. The Hash object passed as the
argument to this tells Active Record to eager load the comments association from the Post model, as
well as the user association for each comment.
When we reload the page in our browser and look at the queries again, theres no longer over a
thousand of them:

Started GET "/posts/1" for 127.0.0.1 at 2013-11-24 11:52:14 +1100


Processing by PostsController#show as HTML
Parameters: {"id"=>"1"}
... SELECT "posts".* FROM "posts"
WHERE "posts"."id" = ? LIMIT 1 [["id", "1"]]
... SELECT "comments".* FROM "comments"
WHERE "comments"."post_id" IN (1)
... SELECT "users".* FROM "users"
WHERE "users"."id" IN (1, 3, 2, 5, 4)

There are now only 3 queries for the data that needs to be displayed on this page: one for the post,
one for the comments and one for all the users for all the comments. The eager loading here has
altered the query for the comments to load it using an IN query, rather than the query it ran before:

... SELECT "comments".* FROM "comments"


WHERE "comments"."post_id" = ? [["post_id", 1]]

This doesnt make too much of a different in the query speeds. The two queries are going to produce
the same result. The major difference here is that the 1000 queries for the users for the comments
has now been reduced to one simple query:
Debugging Rails 30

... SELECT "users".* FROM "users"


WHERE "users"."id" IN (1, 3, 2, 5, 4)

We can also see that the page which once took around 700ms to load (650ms in the view, 25ms in
Active Record), is now loading much, much faster:

Completed 200 OK in 108ms (Views: 51.0ms | ActiveRecord: 3.9ms)

The page is now loading seven times faster with this one easy trick. Thats great!

TODO
Pagination
Fragment caching
(?) Mention the Bullet gem
Handling Exceptions
Not all exceptions known to humankind have been documented in this book so far. There will be
more than these which pop up in the applications that you will write. By default, when users cause
an exception to happen within a Rails application, this is what theyll see:

500 Error Page

Exceptions will happen in your applications, because nobody writes perfect code. Sometimes these
exceptions might happen under a set of very, very specific circumstances and only for one user.
Unless that user contacts you specifically, you may never know that this exception is happening.
Thats where exception notification services come into play. These range from the basic exception_-
notification gem (http://smartinez87.github.io/exception_notification/) which you can install and
configure to email you whenever an exception happens, all the way up to the large scale New Relic
which does much much more than exception notifications. There are others too, such as Airbrake
(http://airbrake.io) and Honeybadger (honeybadger.io).
Advanced Rails Debugging
Advanced Rails Example #1 - Broken Devise
Application
This example was from a debugging session I did with Steven Baker on November 25th, 2013.
In this example well cover:

bundle show
Debugging with the pry gem
How to find where methods are defined

Not all applications that we debug are going to be on the latest and greatest versions of everything.
Sometimes well be asked to debug code that is years old and more often than not written by
somebody else. Unfamiliarity with an older version of Rails or another gem may seem like a huge
hurdle at first, but on closer inspection that hurdle is a lot smaller than it seems.
Debugging sometimes requires a large amount of patience and concentration, as this (almost 5,000
word) example shows. The pay off at the end of such a long debugging session is worth it, in my
opinion. I get a little kick out of it. Lets start debugging.
Here in this application, we are having a problem where (imaginary) users are unable to sign in. The
(imaginary) user is reportedly seeing a 500 error page when they try to sign in, which is indicative of
a bigger problem. Thankfully another (imaginary) developer within this aplication has written a test
to cover this breakage using RSpec + Capybara. This test lives at spec/features/sign_in_spec.rb:

require 'spec_helper'
describe "sign in" do
before do
User.create!(:email => "test@example.com", :password => "password")
end
it "can sign in successfully" do
visit "/"
click_link "Sign in"
fill_in "Email", :with => "test@example.com"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("Signed in successfully.")
end
end
Advanced Rails Debugging 33

When we run this test using bundle exec rspec spec/features/sign_in_spec.rb, well see that
it is indeed breaking:

1) sign in can sign in successfully


Failure/Error: click_button "Sign in"
ActionController::RoutingError:
uninitialized constant SessionsController
# ./spec/features/sign_in_spec.rb:13...

It says uninitialized constant SessionsController, but Devise should be handling the authentication,
shouldnt it? Devise has a controller within its own code called Devise::SessionsController
which should be handling this request, but for some reason it is not. Its trying to use the
SessionsController without the Devise namespace.

Running the test with bundle exec rspec spec/features/sign_in_spec.rb -b yields no better
information, showing us only stacktrace from within Rails or Rack.
Lets investigate this problem ourselves by starting up this application now:

rails server

If we now go to http://localhost:3000 and click Sign in, well see the standard Devise form consisting
of an email and password field.

Sign in form

If we attempt to sign in with test@example.com and password, it will fail and show the same
error that we saw from our tests:
Advanced Rails Debugging 34

Routing error for SessionsController

Why is this happening? The path for this request looks weird. It shows as localhost:3000/session.user.
Where is that coming from? The best first place to look would be back on the sign in page itself to
see if the form tag is sending us there. If we go back to that page, right click on an element of the
form and click Inspect Element, well see that the forms HTML makeup begins with this:

<form accept-charset="UTF-8" action="/session.user" ...>

This explains how were being taken to /session.user, but not the why. Lets investigate how Devise
generates that form now. The template that displays this form lives inside the Devise gem rather than
our application. To get to the template, were going to need to open the current version of Devise
that the application is using. To get there, were going to need to find where Devise is installed on
our system. Lucky for us, theres a command to do that:

bundle show devise

This command will show the path to the gem that the applications Gemfile will use when it sources
its dependencies. It will show something like this:

/Users/user/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/devise-1.1.9

We can open this path in our editor by running this command:

$EDITOR `bundle show devise`

If that doesnt work, then you will need to open the path manually in your editor.
Now that we have Devise open, we need to figure out what template is being shown that renders
this form. We can find that out by looking at the log output from our rails server session when a
request to the sign in page is made:
Advanced Rails Debugging 35

Started GET "/users/sign_in" for 127.0.0.1 ...


Processing by Devise::SessionsController#new as HTML
Rendered .../devise-1.1.9/app/views/devise/shared/_links.erb (0.9ms)
Rendered .../devise-1.1.9/app/views/devise/sessions/new.html.erb
within layouts/application (5.6ms)

In this output, we can see that were making a request to /users/sign_in and that request is being
routed to the new action within Devise::SessionsController. This action then goes on to render
a couple of templates, namely devise/shared/_links.erb and devise/sessions/new.html.erb.
The code for the form is probably going to be in the sessions/new.html.erb template. Inside that
template well see this code to generate the form:

<%= form_for(resource,
:as => resource_name,
:url => session_path(resource_name)) do |f| %>

For the URL of the form, this form_for call is calling the session_path helper, which is probably
a routing helper. We can find out where this is defined within our application by running this
command:

rake routes

This command will show this output for this application:

root /(.:format) ...


new_user_session GET /users/sign_in(.:format) ...
user_session POST /users/sign_in(.:format) ...
destroy_user_session GET /users/sign_out(.:format) ...
user_password POST /users/password(.:format) ...
new_user_password GET /users/password/new(.:format) ...
edit_user_password GET /users/password/edit(.:format) ...
PUT /users/password(.:format) ...
user_registration POST /users(.:format) ...
new_user_registration GET /users/sign_up(.:format) ...
edit_user_registration GET /users/edit(.:format) ...
PUT /users(.:format) ...
DELETE /users(.:format) ...
session POST /session(.:format) ...
new_session GET /session/new(.:format) ...
edit_session GET /session/edit(.:format) ...
GET /session(.:format) ...
PUT /session(.:format) ...
DELETE /session(.:format) ...
Advanced Rails Debugging 36

On the left-hand side of this output, we can see the path helpers defined for the routes if they are
present. We can see that there is one line in particular that defines a session helper:

session POST /session(.:format)


{:action=>"create", :controller=>"sessions"}

This is the helper that is being used by Devise to generate the route for the form incorrectly. Where
is this coming from though? Lets look in config/routes.rb and see if anything there sticks out:

Bug::Application.routes.draw do
root :to => "welcome#index"
devise_for :users
resource :session
end

At the very bottom of this file there is a resource :session line, which is what is defining
the session_path helper. Since there is no SessionsController for this route to use within the
application, it probably got left over in this application from a previous developer. Lets remove this
line from config/routes.rb now:

resource :session

For this route change to take full-effect, well need to restart our rails server session so that the
session_path helper is completely forgotten about. Once weve restarted rails server, we can try
to sign in again with test@example.com and password. This time well see a completely different
error message:

Invalid Email or Password


Advanced Rails Debugging 37

Ok, a different error message is progress because it means something new is happening! First thing
to check: is there actually a user with the email address test@example.com within the database?
The easiest way to check that would be to launch a new console session:

rails c

Within this console session, we want to find if a user with the email test@example.com exists,
which we can do like this:

User.exists?(:email => "test@example.com")

When we run this code, well see that it doesnt exist:

irb(main):001:0> User.exists?(:email => "test@example.com")


=> false

This is happening because the only place where we have been creating this user is within the
test environment, and not the development environment. These two environments use separate
databases and after a test is run within the test environment, that database is emptied anyway.
Therefore this problem may be caused by a simple case of a missing user. Lets create this new user
in the database now:

User.create!(:email => "test@example.com", :password => "password")

With this user created, we can try to sign in again.

Invalid Email or Password


Advanced Rails Debugging 38

Unfortunately, that did not fix the problem yet. The error message is still telling us invalid email and
password, although were pretty certain that its the right email address and the right password. We
can double-check this by first finding the user in the console:

>> user = User.find_by_email("test@example.com")


=> #<User id: 1 ...>

With this, we can see that the user does actually exist. Now lets check to see if the password is valid
using Devises valid_password? method:

>> user.valid_password?("password")
=> true

The user definitely exists and their password is definitely valid, even though the error message is
telling us that this is not true. The error message is lying to us. Lets take a look at the logs which
may give us more leads as to where to look to figure this one out.
Heres what happening for this request:

Started POST "/users/sign_in" for 127.0.0.1 at 2013-11-27 18:03:40 +1100


Processing by Devise::SessionsController#create as HTML
Parameters: ...
Completed in 4ms
Processing by Devise::SessionsController#new as HTML
Parameters: ...
Rendered .../app/views/devise/shared/_links.erb (0.8ms)
Rendered .../app/views/devise/sessions/new.html.erb within layouts/application (\
5.0ms)
Completed 200 OK in 97ms ...

We can see here that Rails is receiving the request from the sign in form and that the request is being
handled by the create action within Devise::SessionsController. Somehow, the action does not
respond in any particular fashion, indicated with the Completed in 4ms message. Typically we
would expect to see a 200 OK or 302 Redirected message there, but were not seeing that. After
this action complese, the new action within the same controller gets the same parameters and then
re-renders the app/views/devise/sessions/new.html.erb template.
To begin to discover whats happening here, lets check out the code within Devise::SessionsController.
If the gem is not open any more, re-open it using this command:

$EDITOR `bundle show devise`

Within Devise, this controller is located at app/controllers/devise/sessions_controller.rb and


its create action looks like this:
Advanced Rails Debugging 39

devise/app/controllers/devise/sessions_controller.rb
1 def create
2 resource = warden.authenticate!(:scope => resource_name, :recall => "new")
3 set_flash_message :notice, :signed_in
4 sign_in_and_redirect(resource_name, resource)
5 end

The create action first calls out to the warden method which is provided by the Warden gem which
Devise depends on. The warden method is a proxy object which keeps track of the current session
state. We can find out where the authenticate! method for this proxy object is defined by firstly
adding the pry gem to our Gemfile:

gem 'pry'

We can install this gem using bundle install. This gem provides us with some useful debugging
tools which well see used as we go along. The first of these is the breakpoint helper called
binding.pry. This stops code execution right in its tracks and will bring up an IRB-esque prompt
which we can then use to debug code. Were going to use this within the create action in
Devise::SessionsController like this:

devise/app/controllers/devise/sessions_controller.rb
1 def create
2 binding.pry
3 resource = warden.authenticate!(:scope => resource_name, :recall => "new")
4 set_flash_message :notice, :signed_in
5 sign_in_and_redirect(resource_name, resource)
6 end

To ensure that our server has this latest and greatest code, well need to restart it. Once its been
restarted, if we try signing in again the browser should hang and then in the server window well
see the Pry prompt:

12: def create


=> 13: binding.pry
14: resource = warden.authenticate!(:scope => resource_name, :recall => "new")
15: set_flash_message :notice, :signed_in
16: sign_in_and_redirect(resource_name, resource)
17: end

We can treat this prompt just like any other Ruby prompt. We can call whatever Ruby code we wish
within it. Right now though, all we want to know is where the authenticate! method is defined.
Lets find that out now by using this code:
Advanced Rails Debugging 40

warden.method(:authenticate!).source_location

The method method returns a Method object that represents the method. Calling source_location
on that object tells us this:

=> [".../gems/warden-1.0.6/lib/warden/proxy.rb", 112]

This indicates to us that the method is defined within the Warden gem, and so we should go look
there to see whats happening. Now that we know where that method is defined, we no longer need
the binding.pry within Devise::SesionsController. We should remove this call now before we
continue.
Lets open the warden gem using this command in our terminal:

$EDITOR `bundle show warden`

Once weve got the gem open, well then open lib/warden/proxy.rb and navigate to line 112 within
that file. The authenticate! method will be here:

warden/lib/warden/proxy.rb:112

1 def authenticate!(*args)
2 user, opts = _perform_authentication(*args)
3 throw(:warden, opts) unless user
4 user
5 end

This method takes any number of arguments and stashes them in the args variable. These arguments
are from Devise::SessionsControllers create action:

resource = warden.authenticate!(:scope => resource_name, :recall => "new")

What resource_name is isnt too important right now. What is important is tracking down why this
method is sending us off to the new action in Devise::SessionsController rather than processing
our request properly.
It would seem here that the _perform_authentication method returns two things: a user and a set
of options. If our authentication is failing, then it would make sense that it would probably be failing
within this _perform_authentication method. A quick search for this method within this file will
show us where it is: down on line 272.
Advanced Rails Debugging 41

warden/lib/warden/proxy.rb:272

1 def _perform_authentication(*args)
2 scope, opts = _retrieve_scope_and_opts(args)
3 user = nil
4 # Look for an existing user in the session for this scope.
5 # If there was no user in the session. See if we can get one from the request.
6 return user, opts if user = user(scope)
7 _run_strategies_for(scope, args)
8 if winning_strategy && winning_strategy.user
9 opts[:store] = opts.fetch(:store, winning_strategy.store?)
10 set_user(winning_strategy.user, opts.merge!(:event => :authentication))
11 end
12 [@users[scope], opts]
13 end

The first thing this method does is call out the _retrieve_scope_and_opts method, passing it the
same args that this method takes. This method is the next method defined in this file:

warden/lib/warden/proxy.rb:289

1 def _retrieve_scope_and_opts(args) #:nodoc:


2 opts = args.last.is_a?(Hash) ? args.pop : {}
3 scope = opts[:scope] || @config.default_scope
4 opts = (@config[:scope_defaults][scope] || {}).merge(opts)
5 [scope, opts]
6 end

We can see what this method returns by altering _perform_authentication to have a binding.pry
right after the _retrieve_scope_and_opts call:

warden/lib/warden/proxy.rb:272

1 def _perform_authentication(*args)
2 scope, opts = _retrieve_scope_and_opts(args)
3 binding.pry

To trigger this breakpoint, well need to restart the server so that the code for all the gems, including
Warden, is reloaded. After that, well try signing in again which will bring up a new Pry prompt:
Advanced Rails Debugging 42

1 272: def _perform_authentication(*args)


2 273: scope, opts = _retrieve_scope_and_opts(args)
3 => 274: binding.pry
4 275: user = nil

We can inspect the values for scope and opts easily in the prompt:

[1] pry(#<Warden::Proxy>)> scope


=> :user
[2] pry(#<Warden::Proxy>)> opts
=> {:scope=>:user, :recall=>"new"}

Now that we know what the value of scope and opts is, we can go through the rest of the _perform_-
authentication methods flow. Lets make sure to remove this binding.pry first, now that weve
found the information we wanted.
The next part of the _perform_authentication method is this:

warden/lib/warden/proxy.rb:277

1 # Look for an existing user in the session for this scope.


2 # If there was no user in the session. See if we can get one from the request.
3 return user, opts if user = user(scope)

This part of the code is pretty well-documented. It attempts to fetch a user from the session, but
there probably isnt one because were attempting to sign in as a user now. If there was a user, then
this method would return the user and the options at this point.
There isnt a user, and therefore its going to fall to the next code within that method:

warden/lib/warden/proxy.rb:280

1 _run_strategies_for(scope, args)
2 if winning_strategy && winning_strategy.user
3 opts[:store] = opts.fetch(:store, winning_strategy.store?)
4 set_user(winning_strategy.user, opts.merge!(:event => :authentication))
5 end

The _run_strategies_for method is called, passing in the scope and args arguments. Lets see
what this method does:
Advanced Rails Debugging 43

warden/lib/warden/proxy.rb:298

1 # Run the strategies for a given scope


2 def _run_strategies_for(scope, args) #:nodoc:
3 self.winning_strategy = @winning_strategies[scope]
4 return if winning_strategy && winning_strategy.halted?
5 if args.empty?
6 defaults = @config[:default_strategies]
7 strategies = defaults[scope] || defaults[:_all]
8 end
9 (strategies || args).each do |name|
10 strategy = _fetch_strategy(name, scope)
11 next unless strategy && !strategy.performed? && strategy.valid?
12 self.winning_strategy = @winning_strategies[scope] = strategy
13 strategy._run!
14 break if strategy.halted?
15 end
16 end

Theres quite a lot going on inside this method. At the top, it sets the winning_strategy virtual
attribute (defined on line 9 of this file), to whatever the output of @winning_strategies[scope] is.
If @winning_strategies[scope] is not nil and is halted?, then the method stops executing and
_perform_authentication continues.

Is @winning_strategies[scope] returning nil? Lets use Pry to find out by sticking a binding.pry
as the first line in that method:

warden/lib/warden/proxy.rb:298

1 def _run_strategies_for(scope, args) #:nodoc:


2 binding.pry
3 self.winning_strategy = @winning_strategies[scope]

We will need to restart our rails server process here for the changes to take effect. Lets try to
sign in again. This will bring up the Pry prompt again:

298: def _run_strategies_for(scope, args) #:nodoc:


=> 299: binding.pry
300: self.winning_strategy = @winning_strategies[scope]

With the Pry prompt, we can see what @winning_strategies[scope] is:


Advanced Rails Debugging 44

[1] pry(#<Warden::Proxy>)> @winning_strategies[scope]


=> nil

Since this call returns nil, the method will continue executing past the line that checks for the
objects presence. Lets keep reading this method.

warden/lib/warden/proxy.rb:303

1 if args.empty?
2 defaults = @config[:default_strategies]
3 strategies = defaults[scope] || defaults[:_all]
4 end

This part of the method is just setting up some defaults for the strategies to use within this method,
unless some arguments explicitly telling us what strategies to use are passed in. With our Pry prompt
still open, we can see if args is empty by simply asking:

[2] pry(#<Warden::Proxy>)> args


=> []

Since args is empty, this will set defaults to the return value of @config[:default_strategies].
Whats that? Lets ask Pry again:

[3] pry(#<Warden::Proxy>)> @config[:default_strategies]


=> {:user=>[:rememberable, :database_authenticatable]}

This method returns the default configured strategies from Devise, namely rememberable and
database_authenticatable. We know that scope is :user, and therefore we can work out from
this that strategies is going to be set to [:rememberable, :database_authenticatable]. We can
see this in practice if we run that code in Pry:

[4] pry(#<Warden::Proxy>)> defaults = @config[:default_strategies]


=> {:user=>[:rememberable, :database_authenticatable]}
[5] pry(#<Warden::Proxy>)> strategies = defaults[scope] || defaults[:_all]
=> [:rememberable, :database_authenticatable]

Now that we know what that part of the method does, lets remove that binding.pry and move on
to the next couple of lines:
Advanced Rails Debugging 45

warden/lib/warden/proxy.rb:308

1 (strategies || args).each do |name|


2 strategy = _fetch_strategy(name, scope)
3 next unless strategy && !strategy.performed? && strategy.valid?

This method iterates through either strategies or args. We know that args is empty, and because
of this the strategies variable is being set, therefore this will iterate through strategies. For each
strategy, the _run_strategies_for method calls _fetch_strategy and passes it the name of the
strategy, as well as the scope variable.
Lets see what this _fetch_strategy method does now; its the last method within this file.

warden/lib/warden/proxy.rb:319

1 # Fetchs strategies and keep them in a hash cache.


2 def _fetch_strategy(name, scope)
3 @strategies[scope][name] ||= if klass = Warden::Strategies[name]
4 klass.new(@env, scope)
5 elsif @config.silence_missing_strategies?
6 nil
7 else
8 raise "Invalid strategy #{name}"
9 end
10 end

Lets stick a binding.pry at the top of this method now, so that we can investigate what its doing:

warden/lib/warden/proxy.rb:319

1 # Fetchs strategies and keep them in a hash cache.


2 def _fetch_strategy(name, scope)
3 binding.pry

Restarting the server and attempting to sign in again will bring up a new Pry prompt:

319: def _fetch_strategy(name, scope)


=> 320: binding.pry
321: @strategies[scope][name] ||= if klass = Warden::Strategies[name]

The first part of this code that will execute is the Warden::Strategies[name] part. Lets see what
that returns now:
Advanced Rails Debugging 46

[1] pry(#<Warden::Proxy>)> Warden::Strategies[name]


=> Devise::Strategies::Rememberable

This call is going to return Devise::Strategies::Rememberable, so that means the if part of the
method will be executed:

warden/lib/warden/proxy.rb:319

1 @strategies[scope][name] ||= if klass = Warden::Strategies[name]


2 klass.new(@env, scope)

This code calls the initialize method located within Devise::Strategies::Rememberable, passs-
ing it the @env object (which is the current requests environment), as well as the scope. This call
returns a new object of the Devise::Strategies::Rememberable class, which is then sent back to
the _run_strategies_for method.
Now that the _run_strategies_for method actually has a strategy to run, it does that with this
line:

warden/lib/warden/proxy.rb:308

1 next unless strategy && !strategy.performed? && strategy.valid?

We know that strategy here is going to be an instance of the Devise::Strategies::Rememberable


class, and thats definitely not nil, so the first check here completes. We dont know however what
performed? or valid? return.

To find that out, lets remove the binding.pry from _fetch_strategy and add a new one directly
above line 308 in warden/proxy.rb:

warden/lib/warden/proxy.rb:308

1 binding.pry
2 next unless strategy && !strategy.performed? && strategy.valid?

Restarting the server and attempting a sign in again will bring up the Pry prompt at the new location:

307: (strategies || args).each do |name|


308: strategy = _fetch_strategy(name, scope)
=> 309: binding.pry

With this prompt, we can now check to see what performed? and valid? return.
Advanced Rails Debugging 47

[1] pry(#<Warden::Proxy>)> strategy.performed?


=> false
[2] pry(#<Warden::Proxy>)> strategy.valid?
=> false

It makes sense here that performed? is returning false, since this is the first time (as far as we have
seen) that the strategy is being fetched and checked. The reason why valid? is returning false is
unclear. So lets investigate where thats coming from so that we can rule it out as a cause of our
problem.
We can find out where the valid? method is defined by using source_location:

>> strategy.method(:valid?).source_location
=> ["...gems/devise-1.1.9/lib/devise/strategies/rememberable.rb", 11]

Lets go back to the open Devise project and find that file. On line 11, it has this code:

devise/lib/strategies/rememberable.rb:11

1 def valid?
2 remember_cookie.present?
3 end

Where is remember_cookie? Its a little further down, near the bottom of the file:

devise/lib/strategies/rememberable.rb:44

1 def remember_cookie
2 @remember_cookie ||= cookies.signed[remember_key]
3 end

This method calls cookies.signed[remember_key]. What is remember_key? Its defined a little


further up:

devise/lib/strategies/rememberable.rb:35

1 def remember_key
2 "remember_#{scope}_token"
3 end

The scope variable here is going to be user, so therefore remember_key is going to be remember_-
user_token. Does remember_cookie return anything? Lets check back in our Pry console:
Advanced Rails Debugging 48

[4] pry(#<Warden::Proxy>)> strategy.send(:remember_cookie)


=> nil

So no, this doesnt return anything. Therefore this particular strategy is not valid. This means that
Warden will now move onto the next strategy. Lets type exit to allow Wardens code to continue
to run. It will stop on the next strategy. We can find out that strategys class by running this:

[1] pry(#<Warden::Proxy>)> strategy.class


=> Devise::Strategies::DatabaseAuthenticatable

This strategy is used by Devise to authenticate user sessions using the database. It finds a user with
an email address and checks that their password is valid. This sounds like exactly the right place to
be looking to find out whats going wrong. Lets look at this very closely.
We can do the same checks as we did with the previous strategy. The strategy does exist, because
we were able to find out the class of it, and that class wasnt NilClass. Next, lets see if this strategy
hasnt been performed yet:

[2] pry(#<Warden::Proxy>)> strategy.performed?


=> false

Ok, so thats the same as the first strategy. Now lets see if this strategy is valid:

[3] pry(#<Warden::Proxy>)> strategy.valid?


=> false

This strategy isnt valid either. Again, theres not much information to go on and so lets investigate
what that valid? method is doing.

[4] pry(#<Warden::Proxy>)> strategy.method(:valid?).source_location


=> [".../gems/devise-1.1.9/lib/devise/strategies/authenticatable.rb", 11]

The valid? method in this strategy is defined like this:

devise/lib/strategies/authenticatable.rb:11

1 def valid?
2 valid_for_http_auth? || valid_for_params_auth?
3 end

What do each of these methods return? Lets see in our Pry console:
Advanced Rails Debugging 49

[5] pry(#<Warden::Proxy>)> strategy.send(:valid_for_http_auth?)


=> false
[6] pry(#<Warden::Proxy>)> strategy.send(:valid_for_params_auth?)
=> false

We should investigate the valid_for_http_auth? method first. Its defined like this:

devise/lib/strategies/authenticatable.rb:36

1 def valid_for_http_auth?
2 http_authenticatable? &&
3 request.authorization &&
4 with_authentication_hash(http_auth_hash)
5 end

Lets see what the first method is doing here:

[7] pry(#<Warden::Proxy>)> strategy.send(:http_authenticatable?)


=> false

The http_authenticatable? method returns false for this strategy, which means that valid_for_-
http_auth? is going to return false as well. Therefore valid_for_http_auth? is a dead-end. Lets
look at valid_for_params_auth?:

devise/lib/strategies/authenticatable.rb:47

1 def valid_for_params_auth?
2 params_authenticatable? && valid_request? &&
3 valid_params? && with_authentication_hash(params_auth_hash)
4 end

Lets see what these methods do.


Advanced Rails Debugging 50

[8] pry(#<Warden::Proxy>)> strategy.send(:params_authenticatable?)


=> true
[9] pry(#<Warden::Proxy>)> strategy.send(:valid_request?)
=> true
[10] pry(#<Warden::Proxy>)> strategy.send(:valid_params?)
=> true
[11] pry(#<Warden::Proxy>)>
strategy.send(:with_authentication_hash, strategy.send(:params_auth_hash))
=> false

Its that final method, with_authentication_hash, that is failing. Lets look at what this method
does:

devise/lib/strategies/authenticatable.rb:104
1 # Sets the authentication hash and the password from params_auth_hash or http_au\
2 th_hash.
3 def with_authentication_hash(hash)
4 self.authentication_hash = hash.slice(*authentication_keys)
5 self.password = hash[:password]
6 authentication_keys.all?{ |k| authentication_hash[k].present? }
7 end

This method takes a hash, namely the one defined by params_auth_hash:

devise/lib/strategies/authenticatable.rb:62
1 # Extract the appropriate subhash for authentication from params.
2 def params_auth_hash
3 params[scope]
4 end

What do the params look like?

{"utf8"=>"",
"authenticity_token"=>"8Lreg3wifRd81HlP0Spo3oQSNrgOhq5odrEAekHqnYQ=",
"user"=>
{"email"=>"test@example.com", "password"=>"password", "remember_me"=>"0"},
"commit"=>"Sign in",
"action"=>"create",
"controller"=>"devise/sessions"}

We know from before that scope is user, so the only parameters used in with_authentication_-
hash are params[:user]:
Advanced Rails Debugging 51

{"email"=>"test@example.com", "password"=>"password", "remember_me"=>"0"}

Now that we know what hash with_authentication_hash has, lets see what the method does next:

devise/lib/strategies/authenticatable.rb:106

1 self.authentication_hash = hash.slice(*authentication_keys)

The hash gets sliced using authentication_keys. What is authentication_keys?

[12] pry(#<Warden::Proxy>)> strategy.send(:authentication_keys)


=> [:login]

The authentication_keys method returns a 1-element array with the symbol :login inside it. This
means that when hash.slice is called within with_authentication_hash, it will pick out any key
called :login from our params[:user] hash. Is there any key with that name?

{"email"=>"test@example.com", "password"=>"password", "remember_me"=>"0"}

No, there isnt! We only have the keys of "email", "password", and "remember_me". It would seem
strange that authentication_keys is returning :login when our hash doesnt contain that key at
all. This is most likely where our bug is happening. If we look at the authentication_keys method,
we can find out where this is coming from:

devise/lib/strategies/authenticatable.rb:111

1 # Holds the authentication keys.


2 def authentication_keys
3 @authentication_keys ||= mapping.to.authentication_keys
4 end

Lets see where that authentication_keys method is coming from:

[13] pry(#<Warden::Proxy>)>
strategy.mapping.to.method(:authentication_keys).source_location
=> ["/Users/ryanbigg/.gem/ruby/2.1.3/gems/devise-1.1.9/lib/devise/models.rb", 22]

The method is defined like this:


Advanced Rails Debugging 52

devise/lib/devise/models.rb:22
1 def self.config(mod, *accessors) #:nodoc:
2 accessors.each do |accessor|
3 mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
4 def #{accessor}
5 if defined?(@#{accessor})
6 @#{accessor}
7 elsif superclass.respond_to?(:#{accessor})
8 superclass.#{accessor}
9 else
10 Devise.#{accessor}
11 end
12 end
13 def #{accessor}=(value)
14 @#{accessor} = value
15 end
16 METHOD
17 end
18 end

Thats a lot of metaprogramming! However, we dont have to look too closely at the metaprogram-
ming to see that authentication_keys is defined inside of another method called self.config. The
metaprogramming in this code would turn into this Ruby code for authentication_keys:

def authentication_keys
if defined?(@authentication_keys)
@authentication_keys
elsif superclass.respond_to?(:authentication_keys)
superclass.authentication_keys
else
Devise.authentication_keys
end
end
def authentication_keys=(value)
@authentication_keys = value
end

With that in mind, we can find out how the value of authentication_keys comes into being.
Lets move the binding.pry to the top of that meta-programmed method and restart our server
again. Well now be at a breakpoint inside the authentication_keys method, and with this we can
determine how the authentication_keys value comes into being.
Lets check if the @authentication_keys variable is defined:
Advanced Rails Debugging 53

[1] pry(User)> @authentication_keys


=> nil

Nope, thats not defined at all. Lets move on to the next statement:

[2] pry(User)> superclass.respond_to?(:authentication_keys)


=> false

Thats also not defined. Therefore it must be in Devise.authentication_keys:

[3] pry(User)> Devise.authentication_keys


=> [:login]

Yes! Thats where this value is coming from. Now that we know we can remove the binding.pry
that we just added.
Since this method is defined on the Devise class itself, we can expect it to be defined in
lib/devise.rb within Devise, because thats where the Devise module itself is defined. Lets check
in that file for authentication_keys. Well come across this code:

lib/devise.rb:64

1 # Keys used when authenticating an user.


2 mattr_accessor :authentication_keys
3 @@authentication_keys = [ :email ]

If we look through this whole file, it looks like the mattr_accessor methods are defining (quite a
lot of) configuration for Devise. It would seem that our authentication_keys value of [:login]
doesnt match the default value, specified here in lib/devise.rb, as [:email]. Something in our
code is changing this. Where could that place be?
Well, we have one big block of Devise configuration in our application at config/initializers/devise.rb.
On line 23 of that file, we have this:

config/initializers/devise.rb:23

1 config.authentication_keys = [:login]

Methods defined on classes and modules in Ruby are typically found in the same files as those classes and modules; its just really best practice
to do so.
Advanced Rails Debugging 54

The culprit has been here all along! Lets try commenting out that line and see what happens when
we restart our application and sign in once again.

Signed in successfully

Great! So now weve been able to sign in successfully. Why was the authentication_keys
setting failing us in the first place though? It was Devises with_authentication_hash method in
Devise::Strategies::Authenticatable which was returning false:

devise/lib/strategies/authenticatable.rb:104

1 # Sets the authentication hash and the password from params_auth_hash or http_au\
2 th_hash.
3 def with_authentication_hash(hash)
4 self.authentication_hash = hash.slice(*authentication_keys)
5 self.password = hash[:password]
6 authentication_keys.all?{ |k| authentication_hash[k].present? }
7 end

The authentication_keys method returned [:login], but authentication_hash didnt contain


that key. It contained this:

{"email"=>"test@example.com", "password"=>"password", "remember_me"=>"0"}

So when the final line of that method is called, it returns false because the authentication_hash
does not contain the :login key. This bubbles up the stack, back to the valid? method for this
strategy:
Advanced Rails Debugging 55

devise/lib/strategies/authenticatable.rb:11

1 def valid?
2 valid_for_http_auth? || valid_for_params_auth?
3 end

That method returns false, which makes Warden not use that strategy at all. When Warden finds
no valid strategy to authenticate against, it ultimately assumes that the authentication attempt was
unsuccessful. This bubbles up and up the stack, back to the authenticate! method:

warden/lib/warden/proxy.rb:112

1 def authenticate!(*args)
2 user, opts = _perform_authentication(*args)
3 throw(:warden, opts) unless user
4 user
5 end

The user variable here is going to be nil because no authentication was successful, so the throw
will happen. The throw method in Ruby requires there to be a catch somewhere, and sure enough
if we look for catch(:warden in Wardens source, well come across one:

warden/lib/warden/manager.rb:30

1 def call(env) # :nodoc:


2 return @app.call(env) if env['warden'] && env['warden'].manager != self
3 env['warden'] = Proxy.new(env, self)
4 result = catch(:warden) do
5 @app.call(env)
6 end
7 result ||= {}
8 case result
9 when Array
10 if result.first == 401 && intercept_401?(env)
11 process_unauthenticated(env)
12 else
13 result
14 end
15 when Hash
16 process_unauthenticated(env, result)
17 end
18 end
Advanced Rails Debugging 56

The catch is there on line 34. The throw is throwing out a Hash object (opts). An Array would be
returned if the @app.call method completed successfully without a throw(:warden) happening.
Therefore the call method will fall to the last couple of lines of this method which calls the
process_unauthenticated method, and we know where it goes from there.

All of this was due to a single line of configuration in config/initializers/devise.rb. A lot of


debugging issues (as weve seen) come down to a single line of code that is wrong or, in this case,
misconfigured. Its rare to come across several lines working together in tandem to make our lives
miserable, but it can happen to.
Thanks to Pry and the method and source_location methods, this bug has been extremely easy to
track down, even if the process was a bit laborious.

Vous aimerez peut-être aussi