Académique Documents
Professionnel Documents
Culture Documents
Ryan Bigg
This book is for sale at http://leanpub.com/debuggingruby
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.
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
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.
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:
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:
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.
1 echo $?
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
$ 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:
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.
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:
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:
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.
The first line of the error is pointing directly to the first line in app/views/posts/new.html.erb:
Debugging Rails 12
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:
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 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:
Due to the debug method calling to_s rather than inspect on the objects it is passed. We must call
inspect ourselves:
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.
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:
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:
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:
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
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.
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:
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
Is this enough to fix our test? Lets find out by running bundle exec rspec spec/features/posts_-
spec.rb.
Debugging Rails 16
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?
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
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.
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.
Well see a similar error when we try to run our automated test, bundle exec rspec spec/features/posts_-
spec.rb:
We can run the recommended command here as well to fix the problem.
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
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:
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
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
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:
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:
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 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:
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:
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:
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:
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
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:
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:
If we try it with a Post object thats fetched from the database, well get this:
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
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
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:
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:
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:
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:
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
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:
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:
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:
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
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:
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:
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
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
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
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
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
We can inspect the values for scope and opts easily in the prompt:
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
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
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
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:
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:
Since args is empty, this will set defaults to the return value of @config[:default_strategies].
Whats that? Lets ask Pry again:
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:
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
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
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
Restarting the server and attempting to sign in again will bring up a new Pry prompt:
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
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
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
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:
With this prompt, we can now check to see what performed? and valid? return.
Advanced Rails Debugging 47
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
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
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:
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:
Ok, so thats the same as the first strategy. Now lets see if this strategy is valid:
This strategy isnt valid either. Again, theres not much information to go on and so lets investigate
what that valid? method is doing.
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
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
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
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
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
{"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
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 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?
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
[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]
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
Nope, thats not defined at all. Lets move on to the next statement:
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
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
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
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.