Skip to content

Commit 05825d1

Browse files
committed
Add docs for macros on the router
1 parent a66e7c3 commit 05825d1

File tree

2 files changed

+123
-55
lines changed

2 files changed

+123
-55
lines changed

guides/routing.md

+12
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ get "/", PageController, :home
5353

5454
`get` is a Phoenix macro that corresponds to the HTTP verb GET. Similar macros exist for other HTTP verbs, including POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE, and HEAD.
5555

56+
> #### Why the macros? {: .info}
57+
>
58+
> Phoenix does its best to keep the usage of macros low. You may have noticed, however, that the `Phoenix.Router` relies heavily on macros. Why is that?
59+
>
60+
> We use `get`, `post`, `put`, and `delete` to define your routes. We use macros for two purposes:
61+
>
62+
> * They define the routing engine, used on every request, to choose which controller to dispatch the request to. Thanks to macros, Phoenix compiles all of your routes to a huge case-statement with pattern matching rules, which is heavily optimized by the Erlang VM
63+
>
64+
> * For each route you define, we also define metadata to implement `Phoenix.VerifiedRoutes`. As we will soon learn, verified routes allows to us to reference any route as if it is a plain looking string, except it is verified by the compiler to be valid (making it much harder to ship broken links, forms, mails, etc to production)
65+
>
66+
> In other words, the router relies on macros to build applications that are faster and safer. Also remember that macros in Elixir are compile-time only, which gives plenty of stability after the code is compiled. As we will learn next, Phoenix also provides introspection for all defined routes via `mix phx.routes`.
67+
5668
## Examining routes
5769

5870
Phoenix provides an excellent tool for investigating routes in an application: `mix phx.routes`.

lib/phoenix/router.ex

+111-55
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ defmodule Phoenix.Router do
66
defexception plug_status: 404, message: "no route found", conn: nil, router: nil
77

88
def exception(opts) do
9-
conn = Keyword.fetch!(opts, :conn)
9+
conn = Keyword.fetch!(opts, :conn)
1010
router = Keyword.fetch!(opts, :router)
11-
path = "/" <> Enum.join(conn.path_info, "/")
11+
path = "/" <> Enum.join(conn.path_info, "/")
1212

13-
%NoRouteError{message: "no route found for #{conn.method} #{path} (#{inspect router})",
14-
conn: conn, router: router}
13+
%NoRouteError{
14+
message: "no route found for #{conn.method} #{path} (#{inspect(router)})",
15+
conn: conn,
16+
router: router
17+
}
1518
end
1619
end
1720

@@ -100,16 +103,40 @@ defmodule Phoenix.Router do
100103
GET /pages/hey/there/world
101104
%{"page" => "y", "rest" => ["there" "world"]} = params
102105
106+
> #### Why the macros? {: .info}
107+
>
108+
> Phoenix does its best to keep the usage of macros low. You may have noticed,
109+
> however, that the `Phoenix.Router` relies heavily on macros. Why is that?
110+
>
111+
> We use `get`, `post`, `put`, and `delete` to define your routes. We use macros
112+
> for two purposes:
113+
>
114+
> * They define the routing engine, used on every request, to choose which
115+
> controller to dispatch the request to. Thanks to macros, Phoenix compiles
116+
> all of your routes to a single case-statement with pattern matching rules,
117+
> which is heavily optimized by the Erlang VM
118+
>
119+
> * For each route you define, we also define metadata to implement `Phoenix.VerifiedRoutes`.
120+
> As we will soon learn, verified routes allows to us to reference any route
121+
> as if it is a plain looking string, except it is verified by the compiler
122+
> to be valid (making it much harder to ship broken links, forms, mails, etc
123+
> to production)
124+
>
125+
> In other words, the router relies on macros to build applications that are
126+
> faster and safer. Also remember that macros in Elixir are compile-time only,
127+
> which gives plenty of stability after the code is compiled. Phoenix also provides
128+
> introspection for all defined routes via `mix phx.routes`.
129+
103130
## Generating routes
104131
105132
For generating routes inside your application, see the `Phoenix.VerifiedRoutes`
106133
documentation for `~p` based route generation which is the preferred way to
107134
generate route paths and URLs with compile-time verification.
108135
109136
Phoenix also supports generating function helpers, which was the default
110-
mechanism in Phoenix v1.6 and earlier. we will explore it next.
137+
mechanism in Phoenix v1.6 and earlier. We will explore it next.
111138
112-
### Helpers
139+
### Helpers (deprecated)
113140
114141
Phoenix generates a module `Helpers` inside your router by default, which contains
115142
named helpers to help developers generate and keep their routes up to date.
@@ -367,30 +394,52 @@ defmodule Phoenix.Router do
367394
opts = resource.route
368395

369396
if resource.singleton do
370-
Enum.each resource.actions, fn
371-
:show -> get path, ctrl, :show, opts
372-
:new -> get path <> "/new", ctrl, :new, opts
373-
:edit -> get path <> "/edit", ctrl, :edit, opts
374-
:create -> post path, ctrl, :create, opts
375-
:delete -> delete path, ctrl, :delete, opts
376-
:update ->
397+
Enum.each(resource.actions, fn
398+
:show ->
399+
get path, ctrl, :show, opts
400+
401+
:new ->
402+
get path <> "/new", ctrl, :new, opts
403+
404+
:edit ->
405+
get path <> "/edit", ctrl, :edit, opts
406+
407+
:create ->
408+
post path, ctrl, :create, opts
409+
410+
:delete ->
411+
delete path, ctrl, :delete, opts
412+
413+
:update ->
377414
patch path, ctrl, :update, opts
378-
put path, ctrl, :update, Keyword.put(opts, :as, nil)
379-
end
415+
put path, ctrl, :update, Keyword.put(opts, :as, nil)
416+
end)
380417
else
381418
param = resource.param
382419

383-
Enum.each resource.actions, fn
384-
:index -> get path, ctrl, :index, opts
385-
:show -> get path <> "/:" <> param, ctrl, :show, opts
386-
:new -> get path <> "/new", ctrl, :new, opts
387-
:edit -> get path <> "/:" <> param <> "/edit", ctrl, :edit, opts
388-
:create -> post path, ctrl, :create, opts
389-
:delete -> delete path <> "/:" <> param, ctrl, :delete, opts
390-
:update ->
420+
Enum.each(resource.actions, fn
421+
:index ->
422+
get path, ctrl, :index, opts
423+
424+
:show ->
425+
get path <> "/:" <> param, ctrl, :show, opts
426+
427+
:new ->
428+
get path <> "/new", ctrl, :new, opts
429+
430+
:edit ->
431+
get path <> "/:" <> param <> "/edit", ctrl, :edit, opts
432+
433+
:create ->
434+
post path, ctrl, :create, opts
435+
436+
:delete ->
437+
delete path <> "/:" <> param, ctrl, :delete, opts
438+
439+
:update ->
391440
patch path <> "/:" <> param, ctrl, :update, opts
392-
put path <> "/:" <> param, ctrl, :update, Keyword.put(opts, :as, nil)
393-
end
441+
put path <> "/:" <> param, ctrl, :update, Keyword.put(opts, :as, nil)
442+
end)
394443
end
395444
end
396445
end
@@ -399,7 +448,10 @@ defmodule Phoenix.Router do
399448
@doc false
400449
def __call__(
401450
%{private: %{phoenix_router: router, phoenix_bypass: {router, pipes}}} = conn,
402-
metadata, prepare, pipeline, _
451+
metadata,
452+
prepare,
453+
pipeline,
454+
_
403455
) do
404456
conn = prepare.(conn, metadata)
405457

@@ -472,13 +524,13 @@ defmodule Phoenix.Router do
472524
def call(conn, _opts) do
473525
%{method: method, path_info: path_info, host: host} = conn = prepare(conn)
474526

527+
# TODO: Remove try/catch on Elixir v1.13 as decode no longer raises
475528
decoded =
476-
# TODO: Remove try/catch on Elixir v1.13 as decode no longer raises
477529
try do
478530
Enum.map(path_info, &URI.decode/1)
479531
rescue
480532
ArgumentError ->
481-
raise MalformedURIError, "malformed URI path: #{inspect conn.request_path}"
533+
raise MalformedURIError, "malformed URI path: #{inspect(conn.request_path)}"
482534
end
483535

484536
case __match_route__(decoded, method, host) do
@@ -490,7 +542,7 @@ defmodule Phoenix.Router do
490542
end
491543
end
492544

493-
defoverridable [init: 1, call: 2]
545+
defoverridable init: 1, call: 2
494546
end
495547
end
496548

@@ -616,9 +668,9 @@ defmodule Phoenix.Router do
616668
quote line: route.line do
617669
def __match_route__(unquote(path), unquote(verb_match), unquote(host)) do
618670
{unquote(build_metadata(route, path_params)),
619-
fn var!(conn, :conn), %{path_params: var!(path_params, :conn)} -> unquote(prepare) end,
620-
&unquote(Macro.var(pipe_name, __MODULE__))/1,
621-
unquote(dispatch)}
671+
fn var!(conn, :conn), %{path_params: var!(path_params, :conn)} ->
672+
unquote(prepare)
673+
end, &(unquote(Macro.var(pipe_name, __MODULE__)) / 1), unquote(dispatch)}
622674
end
623675
end
624676
end
@@ -669,7 +721,7 @@ defmodule Phoenix.Router do
669721
end
670722

671723
defp build_pipes(name, pipe_through) do
672-
plugs = pipe_through |> Enum.reverse |> Enum.map(&{&1, [], true})
724+
plugs = pipe_through |> Enum.reverse() |> Enum.map(&{&1, [], true})
673725
opts = [init_mode: Phoenix.plug_init_mode(), log_on_halt: :debug]
674726
{conn, body} = Plug.Builder.compile(__ENV__, plugs, opts)
675727

@@ -733,15 +785,15 @@ defmodule Phoenix.Router do
733785
defp add_route(kind, verb, path, plug, plug_opts, options) do
734786
quote do
735787
@phoenix_routes Scope.route(
736-
__ENV__.line,
737-
__ENV__.module,
738-
unquote(kind),
739-
unquote(verb),
740-
unquote(path),
741-
unquote(plug),
742-
unquote(plug_opts),
743-
unquote(options)
744-
)
788+
__ENV__.line,
789+
__ENV__.module,
790+
unquote(kind),
791+
unquote(verb),
792+
unquote(path),
793+
unquote(plug),
794+
unquote(plug_opts),
795+
unquote(options)
796+
)
745797
end
746798
end
747799

@@ -786,8 +838,9 @@ defmodule Phoenix.Router do
786838
compiler =
787839
quote unquote: false do
788840
Scope.pipeline(__MODULE__, plug)
789-
{conn, body} = Plug.Builder.compile(__ENV__, @phoenix_pipeline,
790-
init_mode: Phoenix.plug_init_mode())
841+
842+
{conn, body} =
843+
Plug.Builder.compile(__ENV__, @phoenix_pipeline, init_mode: Phoenix.plug_init_mode())
791844

792845
def unquote(plug)(unquote(conn), _) do
793846
try do
@@ -800,6 +853,7 @@ defmodule Phoenix.Router do
800853
Plug.Conn.WrapperError.reraise(unquote(conn), :error, reason, __STACKTRACE__)
801854
end
802855
end
856+
803857
@phoenix_pipeline nil
804858
end
805859

@@ -823,7 +877,7 @@ defmodule Phoenix.Router do
823877

824878
quote do
825879
if pipeline = @phoenix_pipeline do
826-
@phoenix_pipeline [{unquote(plug), unquote(opts), true}|pipeline]
880+
@phoenix_pipeline [{unquote(plug), unquote(opts), true} | pipeline]
827881
else
828882
raise "cannot define plug at the router level, plug must be defined inside a pipeline"
829883
end
@@ -961,32 +1015,32 @@ defmodule Phoenix.Router do
9611015
9621016
"""
9631017
defmacro resources(path, controller, opts, do: nested_context) do
964-
add_resources path, controller, opts, do: nested_context
1018+
add_resources(path, controller, opts, do: nested_context)
9651019
end
9661020

9671021
@doc """
9681022
See `resources/4`.
9691023
"""
9701024
defmacro resources(path, controller, do: nested_context) do
971-
add_resources path, controller, [], do: nested_context
1025+
add_resources(path, controller, [], do: nested_context)
9721026
end
9731027

9741028
defmacro resources(path, controller, opts) do
975-
add_resources path, controller, opts, do: nil
1029+
add_resources(path, controller, opts, do: nil)
9761030
end
9771031

9781032
@doc """
9791033
See `resources/4`.
9801034
"""
9811035
defmacro resources(path, controller) do
982-
add_resources path, controller, [], do: nil
1036+
add_resources(path, controller, [], do: nil)
9831037
end
9841038

9851039
defp add_resources(path, controller, options, do: context) do
9861040
scope =
9871041
if context do
9881042
quote do
989-
scope resource.member, do: unquote(context)
1043+
scope(resource.member, do: unquote(context))
9901044
end
9911045
end
9921046

@@ -1098,18 +1152,20 @@ defmodule Phoenix.Router do
10981152
defmacro scope(path, alias, options, do: context) do
10991153
alias = expand_alias(alias, __CALLER__)
11001154

1101-
options = quote do
1102-
unquote(options)
1103-
|> Keyword.put(:path, unquote(path))
1104-
|> Keyword.put(:alias, unquote(alias))
1105-
end
1155+
options =
1156+
quote do
1157+
unquote(options)
1158+
|> Keyword.put(:path, unquote(path))
1159+
|> Keyword.put(:alias, unquote(alias))
1160+
end
11061161

11071162
do_scope(options, context)
11081163
end
11091164

11101165
defp do_scope(options, context) do
11111166
quote do
11121167
Scope.push(__MODULE__, unquote(options))
1168+
11131169
try do
11141170
unquote(context)
11151171
after

0 commit comments

Comments
 (0)