Elixir /Phoenix — Incomplete and completed todos. Part 5.
Hello, and thanks for checking in. If you haven’t read the previous parts, you can find them here:
- Elixir /Phoenix — Lets code authentication. Todo application part 1.
- Elixir /Phoenix — Lets code authentication. Todo application part 2.
- Elixir /Phoenix — Lets code authentication. Todo application part 3.
- Elixir /Phoenix — A todo and user relationship. Todo application part 4
First of all, let’s remove the “user id” from our table, it looks stupid, and it doesn’t make sense to have it there, when the users are only allowed to see their own todo items:
# web/templates/todo/index.html.eex<h2>Listing todos</h2><table class=”table”>
<thead>
<tr>
<th>Title</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
<%= for todo <- @todos do %>
<tr>
<td><%= todo.title %></td>
<td><%= todo.completed %></td>
<td class=”text-right”>
<%= link “Show”, to: todo_path(@conn, :show, todo),
class: “btn btn-default btn-xs” %>
<%= link “Edit”, to: todo_path(@conn, :edit, todo),
class: “btn btn-default btn-xs” %>
<%= link “Delete”, to: todo_path(@conn, :delete, todo),
method: :delete,
data: [confirm: “Are you sure?”],
class: “btn btn-danger btn-xs” %>
</td>
</tr>
<% end %>
</tbody>
</table><%= link “New todo”, to: todo_path(@conn, :new) %>
Ok, cool. Now, let’s get started on marking todos complete.
First, we need some routes. If we want to invoke functions in our todo controller, we need to correctly route our links/buttons to the correct controller action:
# web/router.ex......
scope “/”, Todo do
......
get “/todos/:id/complete_todo”, TodoController, :complete_todo
put “/todos/:id/complete_todo”, TodoController, :complete_todo
......
end
......
Sweet. We’ll call our new not-yet-written function “complete_todo”. This route will make sure we find it in our todo controller. If we run mix phoenix.routes:
todo_path GET /todos/:id/complete_todo Todo.TodoController :complete_todotodo_path PUT /todos/:id/complete_todo Todo.TodoController :complete_todo
We have both our necessary routes. When the route is activated, we invoke the :complete_todo action in our TodoController. Let’s write it now:
# web/controller/todo_controller.ex......
def complete_todo(conn, %{“id” => id}) do
conn
|> put_flash(:info, “Todo marked as completed”)
|> redirect(to: todo_path(conn, :index))
end
......
First, let’s try and see if we are actually able to invoke our new :complete_todo action through our rotes. We fetch the todo’s id from the conn struct, and put out a nice flash telling us the todo was successfully marked as complete.
We aren’t really marking anything as completed yet, however we can now see that our routes and our controller actions are playing well together.
How can we mark a todo as complete? Well, we have to access our database through our repo, and update our completed field. Let’s take a look at the documentation:
~/todo$ iex -S mixErlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]Interactive Elixir (1.2.3) — press Ctrl+C to exit (type h() ENTER for help)iex> alias Todo.Repo
iex> alias Todo.Todo
iex> h Repo.update@callback update(Ecto.Changeset.t(), Keyword.t()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}Updates a model or changeset using its primary key. In case a model is given, the model is converted .................Example┃ post = MyRepo.get!(Post, 42)
┃ post = Ecto.Changeset.change post, title: “New title”
┃ case MyRepo.update post do
┃ {:ok, model} -> # Updated with success
┃ {:error, changeset} -> # Something went wrong
┃ end
Cool. We can access our database by getting our todo item with Repo.get!. We then update our todo with Ecto.Changeset.change. Lets give it a try:
# web/controllers/todo_controller.ex
......def complete_todo(conn, %{“id” => id}) do
changeset = Repo.get!(Todo, id)
changeset = Ecto.Changeset.change changeset, completed: true case Repo.update(changeset) do
{:ok, todo} ->
conn
|> put_flash(:info, “Todo marked as completed”)
|> redirect(to: todo_path(conn, :index))
{:error, changeset} ->
conn
|> put_flash(:info, “Oops, something went wrong”)
|> redirect(to: todo_path(conn, :index))
end
end
......
Ok. So, we get the todos id from the conn struct. We use this id to get the correct todo item from our database with the help from Repo.get!. We then add the changes to the changeset, setting completed to true. If the Repo are able to update our database successfully, we redirect to our index page, with a nice flash message. If an error raises, we simply return to our index page without any changes:
Awesome! Our todo item is now marked as completed. however, both our completed, and our to-be-completed todos are mixed together in the same table. How can we retrieve only the todos marked as not complete?
We know we have to do some sort of query, so let’s take a look at the documentation:
Ecto.Query# Create a queryquery = from u in “users”,
where: u.age > 18,
select: u.name # Send the query to the repository Repo.all(query)
Cool. Let’s see what we can do with this code example.
First, lets create a couple of functions to retrieve completed and incomplete items from the database:
# web/controllers/todo_controller.ex
......
defp incomplete(user_todos) do
from t in user_todos, where: t.completed == false
enddefp completed(user_todos) do
from t in user_todos, where: t.completed == true
end
......
We follow the code example from the Ecto.Query documentation. To only retrieve the user’s todo items, we are going to put in our previously made function my_todos/1 as an argument. We also want to get the items that are either flagged with completed: false or completed: true.
Now, to actually retrieve the correct todos, we have to do some changes to our index action:
# web/controllers/todo_controller.ex......
def index(conn, _params) do
user = Guardian.Plug.current_resource(conn)
completed_todos = my_todos(user)
|> completed
|> Repo.all
incomplete_todos = my_todos(user)
|> incomplete
|> Repo.all render(conn, “index.html”, incomplete_todos: incomplete_todos,
completed_todos: completed_todos)
end
......
Cool! So we use the pipeline operator to collect and retrieve the correct todos. We have removed our previous todos variable, since we only want to retrieve either completed or incomplete todos. We also make both our new variables available for our view.
Now, let’s change our todo template a bit, and make it a little bit nicer with the help from Twitter Bootstrap’s panels. Twitter Bootstrap comes installed with your phoenix application as default:
# web/templates/todo/index.html.eex<div class=”panel panel-default”>
<div class=”panel-heading”>
<strong class=”h4">To be completed</strong>
<%= link “New Todo”, to: todo_path(@conn, :new),
class: “btn btn-default btn-xs pull-right” %>
</div>
<div class=”panel-body”>
<%= for todo <- @incomplete_todos do %>
<strong class=”h3"><%= todo.title %></strong>
<%= link “Complete”, to: todo_path(@conn, :complete_todo,
todo), class: “btn btn-default btn-sm pull-right” %>
<hr>
<% end %>
</div>
</div><div class=”panel panel-default”>
<div class=”panel-heading”>
<strong class=”h4">Completed Todos</strong>
</div>
<div class=”panel-body”>
<%= for todo <- @completed_todos do %>
<strong class=”h3"><%= todo.title %></strong>
<%= link “Delete”, to: todo_path(@conn, :delete, todo),
method: :delete, data: [confirm: “Are you sure?”],
class: “btn btn-default btn-sm pull-right” %>
<hr>
<% end %>
</div>
</div>
So, we’ll make two panels — one for our incomplete todos, and one for our complete todos. They look almost identical, however I’m choosing to put our complete button in our panel for incomplete todos, and our delete button in our panel for complete todos:
Pretty sweet? Now, go ahead and try and add more todos to the list, mark them as complete, and delete them. Works like a charm!
One thing we should do is to remove our checkbox from the todo creation form:
# web/templates/todo/form.html.eex......
<div class=”form-group”>
<%= label f, :title, class: “control-label” %>
<%= text_input f, :title, class: “form-control” %>
<%= error_tag f, :title %>
</div><div class=”form-group”>
<%= submit “Submit”, class: “btn btn-primary” %>
</div>
......
Since we don’t really need to mark todo’s completed when we create them.
That’s it for now. We finally have a complete, working server-side Todo application.
Thank you for reading my tutorial on how to create a Todo Application with Elixir and the Phoenix Framework. I hope you got some insight in how this beautiful framework works, and that you learned something.
Take care!
Stephan Bakkelund Valois