Debugging Phoenix with IEx.pry

Previous Posts in this series

Brandon Richey
8 min readOct 12, 2015

Writing a Blog Engine in Phoenix and Elixir, Part 1

Writing a Blog Engine in Phoenix and Elixir, Part 2: Authorization

Current Versions

As of the time of writing this, the current versions of our applications are:

  • Elixir: v1.1.1
  • Phoenix: v1.1.0
  • Ecto: v1.1.0
  • Comeonin: v2.0.0

If you are reading this and these are not the latest, let me know and I’ll update this tutorial accordingly.

Introduction

Often in development you end up with a need to interact with a part of your application mid-stream, whether to understand some strange behavior a little better or to mess around with some properties/view context and state/etc. Elixir provides us a nice little toy for doing precisely this and it’s very compatible with Phoenix! If you’re a Ruby developer messing around in the Elixir ecosystem, you may especially be missing tools like pry and byebug, but we get something even better here!

Setup

We’ll create a new Phoenix application to use as our stomping ground for this. I’m feeling particularly unoriginal today, so I’ll just call it “debug”.

mix phoenix.new debug

Answer “y” to installing brunch dependencies. We’ll then follow the rest some of the instructions and do our own thing when it comes to starting up the server:

cd debug
mix ecto.create
iex -S mix phoenix.server

The last command is the most important one: we need to run mix phoenix.server inside the context of an iex shell. Without that, we will not be able to reach out to the shell to debug things.

iex -S mix phoenix.server
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
[info] Running Debug.Endpoint with Cowboy on http://localhost:4000
Interactive Elixir (1.1.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 07 Oct 16:18:34 - info: compiled 5 files into 2 files, copied 3 in 1370ms

We have everything we need to be able to mess around with things, so we’ll start off simple and throw some debugging into our controller. Let’s visit http://localhost:4000/ and make sure the Phoenix starting page shows up before we continue!

Debugging a Controller

We’ll start off by opening up web/controllers/page_controller.ex since that comes default with every Phoenix application. When we open it up by default we see:

defmodule Debug.PageController do
use Debug.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
end

Let’s make it possible to debug from our index action. At the top, we’re going to add the following line before our defmodule Debug.PageController do line:

require IEx

Next, inside of our index function, we’re going to add the following line:

IEx.pry

And then we refresh our browser. We should see the following output in our terminal window:

[info] GET /
[debug] Processing by Debug.PageController.index/2
Parameters: %{}
Pipelines: [:browser]
Request to pry #PID<0.323.0> at web/controllers/page_controller.ex:7. Allow? [Yn]

We’ll answer “Y” here to get into the interactive debugger. Now, we’re in the context of that function call, so we can start to mess around with things. If params was actually being passed, we could inspect it. conn is being passed in and not thrown out, so let’s inspect that:

pry(1)> conn
%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{},
before_send: [#Function<1.27458827/1 in Plug.CSRFProtection.call/2>,
#Function<9.50638171/1 in Phoenix.Controller.fetch_flash/2>,
#Function<0.105544698/1 in Plug.Session.before_send/2>,
#Function<1.26297491/1 in Plug.Logger.call/2>,
#Function<0.65283535/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>],
body_params: %{},
cookies: %{},
halted: false, host: "localhost", method: "GET", owner: #PID<0.323.0>,
params: %{}, path_info: [], peer: {{127, 0, 0, 1}, 62477}, port: 4000,
private: %{Debug.Router => {[], %{}}, :phoenix_action => :index,
:phoenix_controller => Debug.PageController,
:phoenix_endpoint => Debug.Endpoint, :phoenix_flash => %{},
:phoenix_format => "html", :phoenix_layout => {Debug.LayoutView, :app},
:phoenix_pipelines => [:browser],
:phoenix_route => #Function<1.67750312/1 in Debug.Router.match/4>,
:phoenix_router => Debug.Router, :phoenix_view => Debug.PageView,
:plug_session => %{}, :plug_session_fetch => :done}, query_params: %{},
query_string: "", remote_ip: {127, 0, 0, 1},
req_cookies: %{},
req_headers: [{"host", "localhost:4000"}, {"connection", "keep-alive"},
{"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"},
{"upgrade-insecure-requests", "1"},
{"user-agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"},
{"accept-encoding", "gzip, deflate, sdch"},
{"accept-language", "en-US,en;q=0.8"},
{"cookie",""}],
request_path: "/", resp_body: nil, resp_cookies: %{},
resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"},
{"x-request-id", "3bbpa7lnf2t5frotnn7n3svt0hu81pba"},
{"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"},
{"x-content-type-options", "nosniff"}], scheme: :http, script_name: [],
secret_key_base: "mNdPO56AiyV591ayIbXX5ykEHXQAsfJEbEs89ATuHP9yG8GJaMusKtGfglFYrmQw",
state: :unset, status: nil}

We can also access the help on any modules (or for iex in general) using h(). For example, if we just type in h (calling the h function with no arguments), it’ll print out the information for IEx and how to use it. However, you can also feed it a module name and it will give you the documentation for that module. Try it with h(Ecto.Repo) and h(Phoenix.HTML) as tests!

There’s a ton of information here that you now have access to. This is great for poking around at things and learning how things work in general. If we had an Ecto model that we were displaying to a user, for example, here we could attempt a query and look for something that was failing or bad data or anything like that.

If we want to exit out of our debugging window, we have a few options. The first is that we can just Ctrl+C twice to exit the entire shell and give up, but that’s rarely what we want to do! Instead, if you type in respawn() in the pry window, it will continue on with the rest of the request as if you had never initiated the debugger in the first place.

Debugging a View

Debugging a view is basically identical to debugging a controller. If we were to add a new function to our View and call it from the template, we could get dumped there into an IEx pry session. We’d have to modify the code for web/views/page_view.ex to:

require IExdefmodule Debug.PageView do
use Debug.Web, :view
def do_stuff() do
message = "hello"
IEx.pry
end
end

Notice we added the require IEx line and the IEx.pry line to this file. Then inside of our template web/templates/page/index.html.eex we add the following call somewhere:

<% do_stuff() %>

Which dumps us into an IEx.pry window. We can inspect the value of the message variable that we created in the function, for example. You can probably see why this is very very helpful!

Debugging a Template

Inside of a template, we can either require IEx right in the template or we can do it inside of the view that the template is using instead (which is what I personally prefer; I don’t like lots of code hanging out inside of my templates). Still, we can write something like this:

<div class="jumbotron">
<h2>Welcome to Phoenix!</h2>
<p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
<% message = "hello" %>
<% IEx.pry %>
</div>

And then inside of our pry window, do this:

Request to pry #PID<0.393.0> at web/views/page_view.ex:5. Allow? [Yn] yInteractive Elixir (1.1.0) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> message
"hello"
pry(2)>

Debugging a Model

We’ll need to create a model really quick to be able to test this out. We’ll just create a simple User model with a single string field, username.

> mix phoenix.gen.model User users username:string
* creating priv/repo/migrations/20151007211555_create_user.exs
* creating web/models/user.ex
* creating test/models/user_test.exs
Remember to update your repository by running migrations: $ mix ecto.migrate> mix ecto.migrate
Compiled web/controllers/page_controller.ex
Compiled web/models/user.ex
Generated debug app
17:16:38.187 [info] == Running Debug.Repo.Migrations.CreateUser.change/0 forward17:16:38.187 [info] create table users17:16:38.196 [info] == Migrated in 0.0s

Restart the server first and then we’ll jump into web/models/user.ex and toss require IEx at the top. We’ll then modify our changeset function and toss IEx.pry at the top of the function. Finally, we’ll go back into our controller web/controllers/page_controller.ex and ensure that we call the changeset function to trigger this debug window. Modify the index action to read as follows:

def index(conn, _params) do
user = Debug.User.changeset(%User{}, %{username: "test"})
render conn, "index.html"
end

Refresh our browser window and you should see our familiar prompt:

[info] GET /
[debug] Processing by Debug.PageController.index/2
Parameters: %{}
Pipelines: [:browser]
Request to pry #PID<0.309.0> at web/models/user.ex:22. Allow? [Yn]

We’ll answer Y and now we should be inside of our changeset function. We’ll see what we get for our model object and what we get for our params:

pry(1)> model
%Debug.User{__meta__: #Ecto.Schema.Metadata<:built>, id: nil, inserted_at: nil,
updated_at: nil, username: nil}
pry(2)> params
%{username: "test"}

We’ll type in respawn() and carry on our merry way!

Debugging in Tests

The last neat thing we can do with an IEx window is inside of our tests. Open up test/controllers/page_controller_test.exs and add our require statement and pry statements:

require IExdefmodule Debug.PageControllerTest do
use Debug.ConnCase
test "GET /" do
conn = get conn(), "/"
IEx.pry
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
end
end

To run our tests, the command gets a little more complicated. The command that we need to run to make this all work is:

iex -S mix test --trace

Again, we’re just using our normal command to run things but we’re doing so inside of an IEx context (thus the iex -S bit). We’re also adding the — trace option here because if we don’t, the tests will time out automatically and we don’t want that! Beyond that, this is just inside of the context of the test that we threw this command into, so we can inspect conn, for example.

pry(1)> conn
%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...},
assigns: %{layout: {Debug.LayoutView, "app.html"}},
before_send: [#Function<1.7098650/1 in Plug.CSRFProtection.call/2>,
#Function<1.121379662/1 in Phoenix.Controller.fetch_flash/2>,
#Function<0.7621997/1 in Plug.Session.before_send/2>,
#Function<1.1051886/1 in Plug.Logger.call/2>], body_params: %{}, cookies: %{},
halted: false, host: "www.example.com", method: "GET", owner: #PID<0.2140.0>,
params: %{}, path_info: [], peer: {{127, 0, 0, 1}, 111317}, port: 80,
private: %{Debug.Router => {[], %{}}, :phoenix_action => :index,
:phoenix_controller => Debug.PageController,
:phoenix_endpoint => Debug.Endpoint, :phoenix_flash => %{},
:phoenix_format => "html", :phoenix_layout => {Debug.LayoutView, :app},
:phoenix_pipelines => [:browser], :phoenix_recycled => false,
:phoenix_route => #Function<0.121155463/1 in Debug.Router.match/4>,
:phoenix_router => Debug.Router, :phoenix_template => "index.html",
:phoenix_view => Debug.PageView, :plug_session => %{},
:plug_session_fetch => :done, :plug_skip_csrf_protection => true},
query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1},
req_cookies: %{}, req_headers: [], request_path: "/",
resp_body: "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"description\" content=\"\">\n <meta name=\"author\" content=\"\">\n\n <title>Hello Phoenix!</title>\n <link rel=\"stylesheet\" href=\"/css/app.css?vsn=30D2A75\">\n </head>\n\n <body>\n <div class=\"container\" role=\"main\">\n <div class=\"header\">\n <ul class=\"nav nav-pills pull-right\">\n <li><a href=\"http://www.phoenixframework.org/docs\">Get Started</a></li>\n </ul>\n <span class=\"logo\"></span>\n </div>\n\n <p class=\"alert alert-info\" role=\"alert\"></p>\n <p class=\"alert alert-danger\" role=\"alert\"></p>\n\n <div class=\"jumbotron\">\n <h2>Welcome to Phoenix!</h2>\n <p class=\"lead\">A productive web framework that<br />does not compromise speed and maintainability.</p>\n</div>\n\n<div class=\"row marketing\">\n <div class=\"col-lg-6\">\n <h4>Resources</h4>\n <ul>\n <li>\n <a href=\"http://phoenixframework.org/docs/overview\">Guides</a>\n </li>\n <li>\n <a href=\"http://hexdocs.pm/phoenix\">Docs</a>\n </li>\n <li>\n <a href=\"https://github.com/phoenixframework/phoenix\">Source</a>\n </li>\n </ul>\n </div>\n\n <div class=\"col-lg-6\">\n <h4>Help</h4>\n <ul>\n <li>\n <a href=\"http://groups.google.com/group/phoenix-talk\">Mailing list</a>\n </li>\n <li>\n <a href=\"http://webchat.freenode.net/?channels=elixir-lang\">#elixir-lang on freenode IRC</a>\n </li>\n <li>\n <a href=\"https://twitter.com/elixirphoenix\">@elixirphoenix</a>\n </li>\n </ul>\n </div>\n</div>\n\n\n </div> <!-- /container -->\n <script src=\"/js/app.js?vsn=13F765\"></script>\n </body>\n</html>\n",
resp_cookies: %{},
resp_headers: [{"content-type", "text/html; charset=utf-8"},
{"cache-control", "max-age=0, private, must-revalidate"},
{"x-request-id", "8ra1f25r0022s1eorguvmt563edgf961"},
{"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"},
{"x-content-type-options", "nosniff"}], scheme: :http, script_name: [],
secret_key_base: "mNdPO56AiyV591ayIbXX5ykEHXQAsfJEbEs89ATuHP9yG8GJaMusKtGfglFYrmQw",
state: :sent, status: 200}

There you go! You now know how to debug your way into each portion of your Phoenix stack now!

--

--