We’re Fly.io. We run apps for our users on hardware we host around the world. Fly.io happens to be a great place to run Phoenix applications. Check out how to get started!
During my interview with Victor Björklund, I learned how easily the Phoenix generators can be customized for our projects. I was shocked I that I never knew about it and I don’t recall ever seeing it documented. This post is to document this invisible feature that is super easy to use and can be really powerful for teams with either new or mature projects.
Problem
You have an established project and over time, patterns and styles were developed for LiveViews, Controllers, etc. Now, every time you add a new Context, LiveView, Controller or some other standard thing, rather than use the built-in generators, you’ve been copying an existing file and changing it to be what you want.
The process of copying and tweaking files this way is tedious and error prone. We frequently end up with left-overs in our resulting code that points back to where it was copied from.
Is there a way to make creating new standard parts of our application smoother? Can we customize an existing Phoenix generator to make it match our application and our patterns?
Solution
Yes, it’s actually easy to do!
First, let’s be clear about which Phoenix generators we’re talking about.
For your Phoenix project, type mix phx.gen
to list the available Phoenix generators. This is what mine looks like:
mix phx.gen
mix phx.gen.auth # Generates authentication logic for a resource
mix phx.gen.cert # Generates a self-signed certificate for HTTPS testing
mix phx.gen.channel # Generates a Phoenix channel
mix phx.gen.context # Generates a context with functions around an Ecto schema
mix phx.gen.embedded # Generates an embedded Ecto schema file
mix phx.gen.html # Generates context and controller for an HTML resource
mix phx.gen.json # Generates context and controller for a JSON resource
mix phx.gen.live # Generates LiveView, templates, and context for a resource
mix phx.gen.notifier # Generates a notifier that delivers emails by default
mix phx.gen.presence # Generates a Presence tracker
mix phx.gen.release # Generates release files and optional Dockerfile for release-based deployments
mix phx.gen.schema # Generates an Ecto schema and migration file
mix phx.gen.secret # Generates a secret
mix phx.gen.socket # Generates a Phoenix socket handler
Every one of these can easily be customized to generate something more appropriate for our project! Let’s see how!
Choosing a Generator
To get started, let’s pick a generator to customize. It makes sense to choose one that we’ll continue to use as our project evolves. For this example, we’ll use the LiveView generator for a resource. In our case, it’s the mix phx.gen.live
task.
With our generator selected, let’s go!
Step 1: Locate the current template
Luckily, we don’t have to start from scratch! Here’s how we can find the current generator that is used for our project.
Our project’s mix tasks come from our mix dependencies. Specifically, this mix task comes from our Phoenix dependency and lives in deps/phoenix
. The generator templates are found in deps/phoenix/priv/templates
.
Alternatively, we can find the templates online. Here’s a link to the templates on the Phoenix Framework’s main branch. We can use the Github tags to find specific versions of the templates as well.
Step 2: Copy the files we want to customize
Now that we’ve found the existing files to start from, we can copy the templates into our project.
This brings up the next question, “Where should these go?”
To answer this, let’s explore a bit about how the mix task templates work.
How does this work?
The template files are in the Phoenix project’s priv/templates
folder. It turns out that the Phoenix generators first look in our project’s priv/templates
folder for the template files. When the templates aren’t found in our project, and they usually aren’t, it falls back to use the Phoenix project’s templates.
Take-away
If we put one or more customized template files in priv/templates/MIX-TASK-NAME/
then the mix task will look for the file in our project first. Any files we don’t include there will fall back to the Phoenix generator.
The phx.gen.live
directory name is used because that’s the mix task we’re running and where the original templates are located.
This example is using the phx.gen.live
generator. In this case, our files will end up in our project’s priv/templates/phx.gen.live/
directory.
Let’s create that directory in our project.
Step 3: Customize the generated LiveView index.ex
file
Let’s walk through what we need to do:
- copy the file(s) to customize into this directory. For this example, we’ll copy the
index.ex
file which generates the LiveView’s code for the index page. - make a change to
index.ex
. For our example, well just add the comment# CHECK IT OUT! Custom change right here!!!
.
Here’s the first part of our modified priv/templates/phx.gen.live/index.ex
template file. Notice the “CHECK IT OUT!” comment was added.
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Index do
use <%= inspect context.web_module %>, :live_view
alias <%= inspect context.module %>
alias <%= inspect schema.module %>
# CHECK IT OUT! Custom change right here!!!
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :<%= schema.collection %>, <%= inspect context.alias %>.list_<%= schema.plural %>())}
end
Let’s run it and see what happens.
Step 4: Run the generator
Let’s run the mix task using the following command:
To see all the options available for executing this generator, use the help
command like this: mix help phx.gen.live
mix phx.gen.live Foos Foo foos name:string age:integer
The mix task generated a bunch of files. Let’s see the generated index.ex
file:
defmodule Web.FooLive.Index do
use Web, :live_view
alias Web.Foos
alias Web.Foos.Foo
# CHECK IT OUT! Custom change right here!!!
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :foos, Foos.list_foos())}
end
Notice our change (the comment) is included in the newly generated module. The other thing to note is that all the other files were generated without customization.
Perfect! It worked! We customized a Phoenix generator template file and it was used by our project.
Discussion
It turns out that the Phoenix team created an excellent escape hatch for the Phoenix generators!
What I love about this is approach is our customizations are checked-in with the project. Our team members get access to the generators too. As we define new patterns for our project, we now have a good place to put them!
We saw here that we can customize just one, or many of the template files for a generator. It’s totally open for us to decide.
Keeping templates out of our deploy
By default, any templates in our priv/templates
directory will be included in our deploy. There’s nothing inherently dangerous about that, but still, why include dev-focused files in our production build? Luckily, it’s easy to prevent those files from being included.
Assuming we’re using a Dockerfile
to deploy, then adding the following line to our .dockerignore
file excludes any and all templates from the build.
priv/templates/
Nice and easy.
What can we do with this?
With this, we can customize how new LiveViews are generated. We can tailor them for our application, to use our components, our styles, our helper modules, our markup, all the things we used to copying over but often ended up copying too much.
Is it just LiveViews? No! We could customize Contexts with all the functions you end up adding anyway. We could customize migrations, schemas, controllers, or whatever our application uses frequently.
If you’re happy with your established patterns, you can copy them to a brand new project and use it from the beginning.
Cool trick!