The Lazy Programmer's Intro to LiveView: Chapter 3
Tags: liveview, elixir, phoenix
Published: 2023-06-18 15:04:23

[Go to Chapter 1](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487)

## Adding a points system

From the very beginning, I mentioned this project is all about competition, which means we need a way to say a user has a certain amount of points. In this chapter, we are going to create the simplest and dumbest point system so we can use this as an excuse to learn more about what Phoenix auth generator created for us.

The modeling is pretty simple: we are going to add a `points` column to the `users` table and in the meantime, we are going to learn some Ecto.

## What are migrations?

> If you know what migrations are, skip this section, it's meant for beginners.

Development teams need a way of synchronizing changes to their databases. Migrations is a simple technique where all changes to the database are expressed as a migration file that contains the change. If we want to add a column, we need to create a migration file that says 'Please add a column points to my table users with type integer'. This file will be committed to the project and all developers will be able to run it and make their databases be on the latest state. We call applying one or more migrations 'migrating the database'. Remember that after running `mix phx.gen.auth` we needed to do `mix ecto.migrate`? That's it.

Migrations also need to be reversible in case something goes wrong. Using the example above the reverse command would be 'Please remove the column points from the table users'. We call reversing one or more migrations doing a 'rollback of the database'. Say you messed up your `mix phx.gen.auth` and created a table called `userss`, you could easily undo the error with `mix ecto.rollback`.

Usually, web frameworks come with migrations support such as Rails' Active Record, Phoenix uses Ecto and AdonisJS Lucid.  Some frameworks don't come with anything related to databases such as ExpressJS so you'd need to install something like Sequelize which has migrations support too.

Here's what a migration file looks like in AdonisJS:

```
// database/migrations/1587988332388_users.js
import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class extends BaseSchema {
  protected tableName = 'users'

  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.timestamp('created_at', { useTz: true })
      table.timestamp('updated_at', { useTz: true })
    })
  }

  public async down() {
    this.schema.dropTable(this.tableName)
  }
}
```

As you can see there's an `up` and `down` method to teach how to migrate and how to rollback. Also notice the filename uses a timestamp: the reason for that is so migrations must be run in the order they were created to ensure consistency.

Now that you know how roughly a migration looks like and what they're used for, let's get to what matters, Ecto.

## Migrations with Ecto

Ecto migrations are slightly different. They (most of times) don't need to define `up` and `down` methods because the syntax is very aware of how to migrate and rollback it. Let's take the users migrations for example:

```
# 20230617121436_create_users_auth_tables.exs
defmodule Champions.Repo.Migrations.CreateUsersAuthTables do
  use Ecto.Migration

  def change do
    execute "CREATE EXTENSION IF NOT EXISTS citext", ""

    create table(:users) do
      add :email, :citext, null: false
      add :hashed_password, :string, null: false
      add :confirmed_at, :naive_datetime
      timestamps()
    end

    create unique_index(:users, [:email])

    create table(:users_tokens) do
      add :user_id, references(:users, on_delete: :delete_all), null: false
      add :token, :binary, null: false
      add :context, :string, null: false
      add :sent_to, :string
      timestamps(updated_at: false)
    end

    create index(:users_tokens, [:user_id])
    create unique_index(:users_tokens, [:context, :token])
  end
end
```

There's no `up` and `down`, just a `change` method. This means we are confident Ecto can do and undo things with whatever we do there. To make it easier to understand let's break down its blocks:

```
execute "CREATE EXTENSION IF NOT EXISTS citext", ""
```

The `execute/2` function works as `up` and `down`. The first SQL statement is the `up` case and the second one is the `down`. This specific statement says 'When you migrate this, please create the extension [citext](https://www.postgresql.org/docs/current/citext.html) if it does not exist' and the empty string as the second argument means 'don't do anything during rollbacks'.

```
create table(:users) do
  add :email, :citext, null: false
  add :hashed_password, :string, null: false
  add :confirmed_at, :naive_datetime
  timestamps()
end
```

This block tells ecto to create this table during migration and delete it during rollbacks. It's all under the hood because of the [create/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#create/2) function combined with [table/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#table/2) function. Inside the `do` block we can see we ask for 5 fields to be added:

- An `email` of citext (case insensitive text) type non-nullable field.
- A `hashed_password` string type non-nullable field.
- A `confirmed_at`  [naive datetime](https://hexdocs.pm/elixir/1.12.3/NaiveDateTime.html)  [(not aware of timezones)](https://hexdocs.pm/elixir/1.12.3/NaiveDateTime.html) nullable field.
- `timestamps/1` creates `inserted_at` and `updated_at`, both naive datetime non-nullable fields with default values to what their names point at.

```
create unique_index(:users, [:email])
# other things here
create index(:users_tokens, [:user_id])
create unique_index(:users_tokens, [:context, :token])
```

As the name implies, the first [unique_index/3](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#unique_index/3) will create on migration and delete on rollback a unique index for the `users` table under `email` only. [index/3](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#index/3) will generate a regular index and the second unique_index/3 is a composite index under `context` and `token`.

```
create table(:users_tokens) do
  add :user_id, references(:users, on_delete: :delete_all), null: false
  add :token, :binary, null: false
  add :context, :string, null: false
  add :sent_to, :string
  timestamps(updated_at: false)
end
```

You should already be able to understand most of what's happening here but the new things are [references/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#references/2) used to create a foreign key from `users_tokens` to `users` which will make rows from the tokens table be deleted if the foreign key in users is deleted and the fact that this table opts-out of `updated_at` since tokens are never updated.

For more details on Ecto migrations, I recommend reading their [Ecto SQL Ecto.Migration docs and guides](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#module-field-types) but in case you want to learn the possible types for your columns head out to [Ecto docs under Ecto.Schema](https://hexdocs.pm/ecto/Ecto.Schema.html#module-primitive-types). Yes, those are two different docs because Ecto does not necessarily means you need to use one of the default Ecto SQL databases, you could use [Mongo](https://github.com/elixir-mongo/mongodb_ecto) adapter or even something like [ClickHouse](https://github.com/clickhouse-elixir/clickhouse_ecto) which is also SQL.

## Creating our first migration by hand

```
$ mix help ecto.gen.migration

                             mix ecto.gen.migration                             

Generates a migration.

The repository must be set under :ecto_repos in the current app configuration
or given via the -r option.

## Examples

    $ mix ecto.gen.migration add_posts_table
```

Once more generators come to the rescue. We could write it by hand but you'd be having to figure out the current timestamp, write some boilerplate code and all that is boring, let Ecto do that for you. The migration name doesn't impact it's effect but it's nice to be clear what you're going to do:

```
$ mix ecto.gen.migration add_users_points
* creating priv/repo/migrations/20230617145124_add_users_points.exs
```

The default code should be something like:

```
defmodule Champions.Repo.Migrations.AddUsersPoints do
  use Ecto.Migration

  def change do

  end
end
```

Our only migration so far had used `create` to create/delete a database table for us, what if we just want to add a column? [alter/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#alter/2) comes to the rescue.

```
defmodule Champions.Repo.Migrations.AddUsersPoints do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :points, :integer, default: 0, null: false
    end
  end
end
```

No specific `up` and `down` needed, Ecto knows how to do these for you. We will be creating a field called points that default to 0 and can't be null. Let's run this migration. Here's a fun fact: if you pass `--log-migrations-sql` you can see the SQL queries being run:

```
$ mix ecto.migrate --log-migrations-sql

11:58:05.588 [info] == Running 20230617145124 Champions.Repo.Migrations.AddUsersPoints.change/0 forward

11:58:05.590 [info] alter table users

11:58:05.625 [debug] QUERY OK db=9.6ms
ALTER TABLE "users" ADD COLUMN "points" integer DEFAULT 0 NOT NULL []

11:58:05.626 [info] == Migrated 20230617145124 in 0.0s
```

Alright, that should do… For our Postgres database at least. We still need to teach Ecto how to map this column to Elixir. Head out to `lib/champions/accounts/user.ex` and let's add it:

```
defmodule Champions.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime
    field :points, :integer, default: 0

    timestamps()
  end

  # more stuff
end
```

Since we 10x developers and we just added something meaningful to our code, we want to make sure we add tests for this! I didn't mention it to you but the Phoenix auth generator created a lot of test files. You can verify that with `mix test`:

```
$ mix test
................................................................................................................................
Finished in 1.9 seconds (0.6s async, 1.2s sync)
128 tests, 0 failures

Randomized with seed 490838
```

Let's keep it simple and edit an existing test. Head out to `test/champions/accounts_test.exs` and look for 'returns the user if the email exists'. A simple assertion should do:

```diff
test "returns the user if the email exists" do
  %{id: id} = user = user_fixture()
  assert %User{id: ^id} = Accounts.get_user_by_email(user.email)
+ assert user.points == 0
end
```

If running `mix test` still works, we are done with Ecto. At least for now.

## Your first UI change

Our users cannot see how many points they have so far. We can tweak our layout to show their points beside their email. Headout to `lib/champions_web/components/layouts/root.html.heex` and look for `<%= @current_user do %>`.

```diff
<%= if @current_user do %>
  <li class="text-[0.8125rem] leading-6 text-zinc-900">
    <%= @current_user.email %>
+   (<%= @current_user.points %> points)
  </li>
...more stuff
```

## HEEx: your new best friend

HEEx stands for HTML + Embedded Elixir. It's simply the way Phoenix uses to express HTML with Elixir code inside. There are a ton of amazing things behind the scenes to make HEEx a thing but we will defer talking about them to later. What you need to take away from it right now are a few key concepts:

- Whenever you see a tag `<%= elixir code here %>` that elixir code will be rendered into the HTML.
- To run Elixir code and not render anything in the HTML just omit the `=` sign such as `<% x = 10 %>`.
- Any variable inside tags that starts with `@` are called assigns. They're special and we will talk about them a lot over the next chapters. For now, we used the `@current_user` assign to render both the user email and points on the navbar.

For this change, we went to our `root.html.heex` file which contains the outermost HTML of our web page, including the HTML, header, and body tags. Later on, we will be learning more about layouts when it comes to creating multiple of them.

## Summary

- Most Ecto migration codes know how to be migrated and rollbacked without needing two different code paths.
- Ecto `timestamps` function creates `inserted_at` and `updated_at` fields unless the code opts out of one.
- `mix ecto.gen.migration migration_name` does the boilerplate bit of creating migration files with timestamps on names.
- One must also update Ecto models after doing migrations so Ecto knows which fields are available.
- HEEx is how Phoenix templates HTML for interfaces, it uses variables know as `assigns` with names that start with `@` like `@current_user`.
- HEEx has special tags for rendering HTML content from Elixir code `<%= code %>` and just running elixir code without rendering things `<% x = 10 %>`.
- `root.html.heex` contains a simple navbar that shows who's the currently logged user.

[Read chapter 4: Adding points to users](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-4-1nim)