Singular Resources in Phoenix
I recently stumbled across Elixir and Phoenix. The more I dig in, the more I’m drinking the Kool-Aid and loving it. Coming from Rails, things feel familiar, but there has definitely been some grinding of gears as I’ve ramped up.
One of the things I ran into today was how to make a “singular resource” for something like a user profile or account. My first instinct was to do something like this:
# web/router.ex
scope "/", MyApp do
...
resource "/account", UserController
end
That didn’t work. It complained about not finding the function resource/2
:
== Compilation error on file web/router.ex ==
** (CompileError) web/router.ex:31: undefined function resource/2
(phoenix) expanding macro: Phoenix.Router.scope/3
web/router.ex:17: MyApp.Router (module)
(elixir) lib/kernel/parallel_compiler.ex:116: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
At this point, I reached for The Docs, but my awful tethered connection on BART refused to load the page. I consequently started throwing together a ham-fisted solution along the lines of “write all the routes out by hand:”
# web/router.ex
scope "/", MyApp do
...
# Dear future self, I'm sorry. =/
get "/account", UserController, :show
get "/account/edit", UserController, :edit
put "/account", UserController, :update
...
end
That was all fine and great until I printed out the routes and realized I should probably handle PATCH
on :update
in addition to PUT
:
# web/router.ex
scope "/", MyApp do
...
# Dear future self, I'm sorry. =/
get "/account", UserController, :show
get "/account/edit", UserController, :edit
put "/account", UserController, :update
patch "/account", UserController, :update
...
end
While I’m relatively sure that this should work, it just felt gross. Thankfully, by this point my phone felt like connecting to the network again, so I was able to pull up the docs. I quickly found resources/4
and noticed the singleton
option. There’s a helpful blurb right afterward that perfectly matches what I was looking for:
Singleton resources
When a resource needs to be looked up without referencing an ID, because it contains only a single entry in the given context, the
:singleton
option can be used to generate a set of routes that are specific to such single resource:
[…]
Usage example:
resources "/account", AccountController, only: [:show], singleton: true
So there you go. All my artisanal hand-crafted routes can be replaced with a single call to resources
:
# web/router.ex
scope "/", MyApp do
...
resources "/account", UserController, singleton: true
...
end