Nested Routes and Associations - Elixir and Phoenix

We are going to continue our todoapp by adding 2 models, Task and Comment. We will use generators (scaffolds) for both models. The Task will have many Comments and a Comment will belong to a Task. As a bonus, lets use a nested route.

To begin, let’s generate our full Task scaffold.

mix phoenix.gen.html Task tasks name:string body:text

The instructions following this command are important. Ensure you add the code

resources '/tasks', TasksController

Then lets add the table to our database.

mix ecto.migrate

To see all that mix offers, use this

1
2
3
4
5
6
7
8
9
$ mix help | grep -i phoenix
mix phoenix.digest      # Digests and compress static files
mix phoenix.gen.channel # Generates a Phoenix channel
mix phoenix.gen.html    # Generates controller, model and views for an HTML based resource
mix phoenix.gen.json    # Generates a controller and model for a JSON based resource
mix phoenix.gen.model   # Generates an Ecto model
mix phoenix.new         # Create a new Phoenix v1.1.2 application
mix phoenix.routes      # Prints all routes
mix phoenix.server      # Starts applications and their servers

The Comment generator is just as easy.

mix phoenix.gen.html Comment comments body:text task_id:integer

Again, we need to follow the instructions after this generator.

Now lets open web/router.ex and make our nested routes.

1
2
3
resources '/tasks', TasksController do
    resources '/comments', CommentsController 
end

After this change, we will want to search the project and repair any URL helpers that no longer make sense.

Issuing mix phoenix.routes will show you what routes are available.

For us, comments_path is now task_comments_path and so forth. This is a bit tedious and can be avoided by creating the generator files manually.

We can now open each model and associate them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
defmodule Todoapp.Task do
  use Ecto.Schema

  schema "tasks" do
    field :name
    field :body
    has_many :comments, Todoapp.Comment
    timestamps
  end
end

defmodule Todoapp.Comment do
  use Ecto.Schema

  schema "comments" do
    field :body
    belongs_to :task, Todoapp.Task
    timestamps
  end
end

Now to use this relation in a view, for example

1
2
3
<%= for comment ← @task.comments %>
<%= comment.body %>
<% end %>

We will need to preload the association. To do this, we can prepare the data in the controller

1
2
task = Repo.get!(Task, 1)
task_with_comments = Repo.preload(task, :comments)

And the associations now work with the view!