“Developing Phoenix apps with IEx.pry is da bomb!”

— Aloha Stadium Swap Meet vendor (not)

The Phoenix Guides were good, but in a year since I’ve last looked at them, they’ve become even better! Currently reading Programming Phoenix ≥1.4, and even though the book goes more into the details, IEx.pry ¹ makes it easier to understand certain constructs.

Let’s take the Plug.Conn struct. It holds all the information about a connection, from low-level details, such as request/response headers and form submission data, to framework-specific add-ons, such as the one-off flash messages.

Understanding and getting closely familiar with the Plug.Conn struct is crucial, because a Phoenix application is just a large function call making transformations on it. The Cowboy web server parses the incoming request, hands off the Plug.Conn struct to the Phoenix web application, that in turn extracts the needed information and gradually builds up a response to hand it back to Cowboy to send the response to the client.²

A Phoenix application can be broken down to individual functions, that are neatly organized into pipelines at certain stages of the request/response life cycle. Almost all of these functions take a Plug.Conn struct as their first argument, making any transformation explicit, easy to follow and, with IEx’s tools, make it possible to inspect at any point.

Set up the test app

Being lazy and reusing the code from Contexts chapter from the Phoenix Guides:

Or put these in a file,

See footnote 3 for resources for the `sed` command

and execute it with source setup.sh on the terminal.

This results in an app with a form to create users.

Inspect the Plug.Conn struct

Update create function inlib/puffin_web/controllers/user_controller.ex with require IEx; IEx.pry :

Start the server with iex -S mix phx.server ,
visit http://localhost:4000/users/new
and create a new user.

After hitting “Submit”, check to command line and you should be greeted with similar to the following:

Once in the IEx repl, explore conn to your hearts desire. The form data will be in conn.params .

pry(2)> conn.params
%{
"_csrf_token" => "LDFwACkgBQkrKiFfIkZnahhhEyU9AAAAMD5FMXixObLfu/3/T9bfog==",
"_utf8" => "✓",
"user" => %{"name" => "The Cake", "username" => "Is a lie"}
}

Enter respawn() to continue the execution of the request.

How are flash messages stored in Plug.Conn?

Inserting an anonymous plug (i.e., a function that “receives a connection and a set of options as arguments and returns the connection” ):

Once on IEx:

pry(1)> conn.private.phoenix_flash
%{"info" => "User created successfully."}
pry(2)> conn.params
%{
"_csrf_token" => "TkMUF1xKDiIsMVUBAwBmAiBoKxocNgAA/6QQ82bSHy88Ti2Gl0ZYNQ==",
"_utf8" => "✓",
"user" => %{"name" => "Kilgore", "username" => "Troutman"}

The private fields in Plug.Conn “are reserved for libraries/framework usage”.

pry(3)> conn.private
%{
PuffinWeb.Router => {[], %{}},
:phoenix_action => :create,
:phoenix_controller => PuffinWeb.UserController,
:phoenix_endpoint => PuffinWeb.Endpoint,
:phoenix_flash => %{"info" => "User created successfully."},
:phoenix_format => "html",
:phoenix_layout => {PuffinWeb.LayoutView, :app},
:phoenix_pipelines => [:browser],
:phoenix_router => PuffinWeb.Router,
:phoenix_view => PuffinWeb.UserView,
:plug_session => %{"_csrf_token" => "auEFdxlqdHm9WiTELXqCRg=="},
:plug_session_fetch => :done
}

More

Check out IEx.Pry documentation, because this has only been scratching the surface so far.

  1. [^] Brandon Richey’s Debugging Phoenix with IEx.pry (and his other) posts have been a great help. Thank you!
  2. [^] Is this correct?
    ( https://github.com/toraritte/knowledge-gaps/issues/25 )
  3. [^] Stackoverflow threads:
    how to insert a line using sed before a pattern and after a line number?
    Using sed, Insert a line below (or above) the pattern? [duplicate]
  4. [^] Usingsource is needed because “Without sourcing the changes, [execution] will happen in the sub-shell and not in the parent shell which is invoking the script”. (That is, using bash setup.sh will do the same, but you will have to cdinto the directory.)
  5. [^] The full Plug.Conn struct:
pry(1)> conn
%Plug.Conn{
adapter: {Plug.Adapters.Cowboy.Conn, :...},
assigns: %{},
before_send: [#Function<0.30499245/1 in Plug.CSRFProtection.call/2>,
#Function<4.102068688/1 in Phoenix.Controller.fetch_flash/2>,
#Function<0.98347970/1 in Plug.Session.before_send/2>,
#Function<1.31498290/1 in Plug.Logger.call/2>,
#Function<0.83616950/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>],
body_params: %{
"_csrf_token" => "LDFwACkgBQkrKiFfIkZnahhhEyU9AAAAMD5FMXixObLfu/3/T9bfog==",
"_utf8" => "✓",
"user" => %{"name" => "The Cake", "username" => "Is a lie"}
},
cookies: %{
"JSESSIONID" => "1f44zy2txsvht1ehzjv6oo80ur",
"_context_tutorial_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYWk1tRVJldko5ZlhldW
1sbnR4SEwydz09.23kVobuYLAs3Cjl4bFzIVqYtJP6Ygl7y432p-_dQuMA",
"_hello_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYaVlsNE8wRGZhT1F3K0VaQWZHK282U
T09.xZBADYxNgHNZcOsfwKF0D58qmrX0RTXQ_Vq_B-9g9gc",
"_puffin_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYYXVFRmR4bHFkSG05V2lURUxYcUNS
Zz09.k9rqdcNvy0ZeZQAxRPm6M9W5iF9VVnpyIW_PfjFjN6M",
"_rum_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYekJFSlNTVW93b1RuZUdsbWZSZUpldz0
9.wXwOj-8LIifl2rSh3wcxEfIr3d9OezN1BSr9-v52xTE"
},
halted: false,
host: "localhost",
method: "POST",
owner: #PID<0.386.0>,
params: %{
"_csrf_token" => "LDFwACkgBQkrKiFfIkZnahhhEyU9AAAAMD5FMXixObLfu/3/T9bfog==",
"_utf8" => "✓",
"user" => %{"name" => "The Cake", "username" => "Is a lie"}
},
path_info: ["users"],
path_params: %{},
port: 4000,
private: %{
PuffinWeb.Router => {[], %{}},
:phoenix_action => :create,
:phoenix_controller => PuffinWeb.UserController,
:phoenix_endpoint => PuffinWeb.Endpoint,
:phoenix_flash => %{},
:phoenix_format => "html",
:phoenix_layout => {PuffinWeb.LayoutView, :app},
:phoenix_pipelines => [:browser],
:phoenix_router => PuffinWeb.Router,
:phoenix_view => PuffinWeb.UserView,
:plug_session => %{"_csrf_token" => "auEFdxlqdHm9WiTELXqCRg=="},
:plug_session_fetch => :done
},
query_params: %{},
query_string: "",
remote_ip: {127, 0, 0, 1},
req_cookies: %{
"JSESSIONID" => "1f44zy2txsvht1ehzjv6oo80ur",
"_context_tutorial_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYWk1tRVJldko5ZlhldW
1sbnR4SEwydz09.23kVobuYLAs3Cjl4bFzIVqYtJP6Ygl7y432p-_dQuMA",
"_hello_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYaVlsNE8wRGZhT1F3K0VaQWZHK282U
T09.xZBADYxNgHNZcOsfwKF0D58qmrX0RTXQ_Vq_B-9g9gc",
"_puffin_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYYXVFRmR4bHFkSG05V2lURUxYcUNS
Zz09.k9rqdcNvy0ZeZQAxRPm6M9W5iF9VVnpyIW_PfjFjN6M",
"_rum_key" => "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYekJFSlNTVW93b1RuZUdsbWZSZUpldz0
9.wXwOj-8LIifl2rSh3wcxEfIr3d9OezN1BSr9-v52xTE"
},
req_headers: [
{"host", "localhost:4000"},
{"connection", "keep-alive"},
{"content-length", "144"},
{"cache-control", "max-age=0"},
{"origin", "http://localhost:4000"},
{"upgrade-insecure-requests", "1"},
{"content-type", "application/x-www-form-urlencoded"},
{"user-agent",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.
62 Safari/537.36"},
{"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"},
{"referer", "http://localhost:4000/users/new"},
{"accept-encoding", "gzip, deflate, br"},
{"accept-language", "en-US,en;q=0.9,hu;q=0.8"},
{"cookie",
"JSESSIONID=1f44zy2txsvht1ehzjv6oo80ur; _hello_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm
0AAAAYaVlsNE8wRGZhT1F3K0VaQWZHK282UT09.xZBADYxNgHNZcOsfwKF0D58qmrX0RTXQ_Vq_B-9g9gc; _context_t
utorial_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYWk1tRVJldko5ZlhldW1sbnR4SEwydz09.23kVo
buYLAs3Cjl4bFzIVqYtJP6Ygl7y432p-_dQuMA; _rum_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYe
kJFSlNTVW93b1RuZUdsbWZSZUpldz09.wXwOj-8LIifl2rSh3wcxEfIr3d9OezN1BSr9-v52xTE; _puffin_key=SFMyN
TY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYYXVFRmR4bHFkSG05V2lURUxYcUNSZz09.k9rqdcNvy0ZeZQAxRPm6M9
W5iF9VVnpyIW_PfjFjN6M"}
],
request_path: "/users",
resp_body: nil,
resp_cookies: %{},
resp_headers: [
{"cache-control", "max-age=0, private, must-revalidate"},
{"x-frame-options", "SAMEORIGIN"},
{"x-xss-protection", "1; mode=block"},
{"x-content-type-options", "nosniff"},
{"x-download-options", "noopen"},
{"x-permitted-cross-domain-policies", "none"}
],
scheme: :http,
script_name: [],
secret_key_base: "7o5VOymmJ1In2lvK5eqIu3L8ExVzdUZAa2JdxGS/A8pjxf8BcZkMqLISTESWeDZv",
state: :unset,
status: nil
}

 by the author.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store