Where are all these points coming from?
Tags: elixir, phoenix, liveview, backend
Published: 2023-07-06 13:22:18

Where's the fun of winning if people can't know who lost to you? With sportsmanship in our best interest, let's start recording players and outcomes for matches and in the meantime, we will learn some interesting stuff from Ecto and LiveView.

## Storing new stuff means we need a new model

Currently, we have a User model and two other related models that Phoenix created for auth stuff. I just told you we need to store match outcomes so that means we need to create a new model. Remember I told you that to change a model we need a migration? The same goes to create a new one.

We are going to create a table called `matches` that will contain 3 pieces of information: who's player one, who's player two and what's the result of that match. We start that by generating the migration file:

```
$ mix ecto.gen.migration create_matches
* creating priv/repo/migrations/20230702143738_create_matches.exs
```

Now we need to edit that to add the information we want:

```elixir
defmodule Champions.Repo.Migrations.CreateMatches do
  use Ecto.Migration

  def change do
    create table(:matches) do
      add :result, :string
      add :user_a_id, references(:users, on_delete: :delete_all)
      add :user_b_id, references(:users, on_delete: :delete_all)

      timestamps()
    end

    create index(:matches, [:user_a_id])
    create index(:matches, [:user_b_id])
  end
end
```

Just a quick reminder: the `change` method means Ecto knows how to run both the migration and rollback methods for this. Since we are using `create table(:matches)`  ecto knows that migrating means `create table matches…` and rolling back means `drop table matches`.

As for the fields, the first one will be called `:result` which we will talk more about later.  As for the other fields we have `:user_a_id` and `:user_b_id` both using the function [references/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#references/2) to link those IDs to the `users` table. Let's talk about these.

First of all the naming choice. I've been calling our users as 'players' sometimes and it definitely would make sense to call those `player_a_id` and `player_b_id`. I just think it's easier to reason about this code if the field is called `user` since the referenced table is called `users`.

The other thing you might have paid attention to is the keyword `on_delete: :delete_all`. That means if that specific user were to be deleted this match will also be deleted. This might sound scary right now but I'm making a decision of making user records not delectable. That would cause issues with things like GDPR but since I won't be risking making my database invalid, when the time comes for the app to need users to be able to delete all their data I'll go for the path of obfuscating all [Personal Identifiable Information (PII)](https://www.dol.gov/general/ppii) so John Doe would become 'Unknown User ABC'.

We also add indexes to the foreign keys so it's faster to search for them and trust me we are going to be a lot of listing on those. Don't forget to run `mix ecto.migrate`. If you do it's no big issue, Phoenix will show a big error message with a button to run this the next time you go to your app.

Now there are 3 implicit columns being created there. First of all, when you use [table/2](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#table/2) on an Ecto migration, you're by default creating a field called `id`. You can opt-out of that but in this case, we want this so we can easily sort, locate, delete, and update them. Next, there's [timestamps/1](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#timestamps/1) which adds `inserted_at` and `updated_at` fields. `inserted_at` will be great to know when a match was declared and `updated_at` could be useful in the future to track updates when this table gets more features.

## Contexts strike again

In the Phoenix world, context modules dictate the business rules of our app. Up until today we only used the generated `Champions.Accounts` module because we only edited things on our `users` table. That made sense at that time but we are outgrowing the Accounts module and starting something bigger and more and more not so related to the main goal of Accounts which is managing users.

We will start a new context called Ranking that will contain things related to matches and points. All you need to do to create a new module is create a new file under `lib/champions`.

```elixir
# lib/champions/ranking.ex
defmodule Champions.Ranking do
  @moduledoc """
  The Ranking context.
  """
end
```

Later on, we will reason about moving functions from Accounts to here but let's focus on the new code first.

## An Ecto migration needs an Ecto model

Ecto migrations only change how data would be stored in your database, you can say it uses [Data Definition Language (DDL)](https://www.postgresql.org/docs/current/ddl.html) if you're into details. But after that, you need to prepare our Elixir app to manage that data, which you can call [Data Manipulation Language (DML)](https://www.postgresql.org/docs/current/dml.html) if you want to be fancy.

The first step to creating a model is to define the module. Since we are working under the `Champions.Ranking` module it makes sense for our model to live under `Champions.Ranking.Match` and of course, the folder structure should match so the file will live under `lib/champions/ranking/`.

```elixir
# lib/champions/ranking/match.ex
defmodule Champions.Ranking.Match do
  use Ecto.Schema
  import Ecto.Changeset

  schema "matches" do
    field :result, Ecto.Enum, values: [:winner_a, :winner_b, :draw]
    field :user_a_id, :integer
    field :user_b_id, :integer

    timestamps()
  end

  @doc false
  def changeset(match, attrs) do
    match
    |> cast(attrs, [:result, :user_a_id, :user_b_id])
    |> validate_required([:result, :user_a_id, :user_b_id])
    |> foreign_key_constraint(:user_a_id)
    |> foreign_key_constraint(:user_b_id)
  end
end
```

There's a lot to unpack here, some of those things you've already seen before but lets take our time to analyze everything bit by bit here.

First of all, this is an elixir module. There's no magic here, `defmodule Champions.Ranking.Match` says it all. The fun begins in line 3, the `use Ecto.Schema` means we are using a macro. I promise we will look into how [use](https://elixir-lang.org/getting-started/alias-require-and-import.html#use) works later but for now, trust me that `use Ecto.Schema` makes your module `Champions.Ranking.Match` also be available as a struct like `%Champions.Ranking.Match{user_a_id: 1, user_b_id: 2, result: :winner_a}`.

In the next line, we import `Ecto.Changeset` so we have neat features for data validation. If we didn't import we could be still using all those functions but we'd need to type `Ecto.Changeset.cast` and `Ecto.Changeset.validate_required` and that takes soo long we might as well import all functions already. And you just learned how Elixir's [import](https://elixir-lang.org/getting-started/alias-require-and-import.html#import) keyword works.

Lines 6 to 12 are where the fun begins. The [schema/2](https://hexdocs.pm/ecto/Ecto.Schema.html#schema/2) macro receives first the table name followed by a `do` block. We need the table name to be the same as our migration so since we did `create table(:users)` here we say `schema "users" do`. Now let's talk about the `do` block. Inside it, we do something very similar to our migration except we define fields that will be available inside our struct. The user IDs are very simple since Ecto will map Postgres IDs to Elixir integers we just say they're of the type `:integer`. And at the end, you will notice there is [timestamps/1](https://hexdocs.pm/ecto/Ecto.Schema.html#timestamps/1) again. The last time we saw that function it was from `Ecto.Migration` now this one is from `Ecto.Schema` but as you guessed it, it adds `inserted_at` and `updated_at` to your model.

The new type here you should be curious about is [Ecto.Enum](https://hexdocs.pm/ecto/Ecto.Enum.html). So far you've only seen types in the form of atom such as `:integer` and `:string` but this time the type is a module. This is an [Ecto custom type](https://hexdocs.pm/ecto/Ecto.Type.html#module-custom-types-and-primary-keys). More specifically, this is a built-in custom type so you don't have to install anything. What it does is help us validate values users try to place on `:result` and remember you, the programmer, is also an user so whatever validation we can get we take it.

Remember that our migration only said `:result` is a `:string` so your database doesn't know anything about enums, this is all in the `Ecto.Model`. If we ever need to stop using `Ecto.Enum` all we need to do is change this type to `:string` and maybe fix some tests.

Last but not the least, when we define an `Ecto.Model` we need to create at least one changeset to control how the outside world manipulates this data. Ours is pretty simple, we `cast` and `validate_required` all fields then do something you haven't seen so far: [foreign_key_constraint/3](https://hexdocs.pm/ecto/Ecto.Changeset.html#foreign_key_constraint/3). This validation is a delayed check on the foreign keys. If you try to `Repo.insert` or `Repo.update` this changeset, if the foreign keys don't match, Ecto will gracefully handle the error.

Our database knows about our table, we have a model to manipulate such table. The next step is to create functions in our module to make this process easier for anyone who needs.

## Adding functions to our Ranking context

```elixir
defmodule Champions.Ranking do
  @moduledoc """
  The Ranking context.
  """

  import Ecto.Query, warn: false
  alias Champions.Repo

  alias Champions.Ranking.Match

  @doc """
  Creates a match.

  ## Examples

      iex> create_match(%{field: value})
      {:ok, %Match{}}

      iex> create_match(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_match(attrs \\ %{}) do
    %Match{}
    |> Match.changeset(attrs)
    |> Repo.insert()
  end
end
```

To start we can be simple, we need only a `create_match/1` function. After that we need to teach the places that generate matches how to save them. Let's start with our `Champions.Accounts.declare_draw_match/2` .

```diff
defmodule Champions.Accounts do
  alias Champions.Ranking
# a lot of things
  def declare_draw_match(user_a, user_b) do
+   {:ok, _match} = Ranking.create_match(%{
+     user_a_id: user_a.id,
+     user_b_id: user_b.id,
+     result: :draw
+   })
    {:ok, updated_user_a} = increment_user_points(user_a, 1)
    {:ok, updated_user_b} = increment_user_points(user_b, 1)
    {:ok, updated_user_a, updated_user_b}
  end
# a lot of things
end
```

Don't forget to `alias Champions.Ranking` in the top otherwise you're going to get an error. There's no mystery here, we just used the `create_match/2` function to start recording those matches. Now let's go see what it needs to be done for conceding losses:

```diff
defmodule Champions.Accounts do
  alias Champions.Ranking
# a lot of things
- def concede_loss_to(winner) do
+ def concede_loss_to(loser, winner) do
+   {:ok, _match} = Ranking.create_match(%{
+     user_a_id: loser.id,
+     user_b_id: winner.id,
+     result: :winner_b
+   })
    increment_user_points(winner, 3)
  end
# a lot of things
end
```

This time we need to do something to the function signature. `concede_loss_to/1` became `concede_loss_to/2` because we also need to know who is losing here for the record. Needless to say, you'll need to look for all places that use `Accounts.concede_loss_to/1` and change. You could do that by global searching `"concede_loss_to("` but if you run `mix test` right now you will see a ton of errors, some from LiveViews and some from tests itself too. Let's quickly address those so we can move on:

```diff
# lib/champions_web/live/user_live/show.ex
- def handle_event("concede_loss", _value, %{assigns: %{user: user}} = socket) do
+ def handle_event("concede_loss", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
-   {:ok, updated_user} = Accounts.concede_loss_to(current_user, user)
+   {:ok, updated_user} = Accounts.concede_loss_to(user)
    {:noreply, assign(socket, :user, updated_user)}
  end
```

Fixing that LiveView will also fix its test.

```diff
# test/champions/accounts_test.exs
- describe "concede_loss_to/1" do
+ describe "concede_loss_to/2" do
    test "adds 3 points to the winner" do
+     loser = user_fixture()
      user = user_fixture()
      assert user.points == 0
-     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(user)
+     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(loser, user)
    end
  end
```

For now, we just fixed this test error but we will come back to also test that the Match was created. Now your tests should be green.

## How do I see data when there's no UI?

If you have a Postgres client such as DBeaver or Postico you probably don't need this right now but in case you don't know about IEx I'll show you something very interesting in Elixir right now.

`iex` ([Interactive Elixir](https://hexdocs.pm/iex/1.14/IEx.html)) is a command that comes with Elixir by default that lets enter a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) where you can quickly test Elixir code just as you would do with JavaScript's console in your browser. If you are inside a Mix project folder and run `iex -S mix` you tell IEx that you also want to be able to do things using code from your project. Let's do this so we can list all our Matches.

```elixir
$ iex -S mix
Erlang/OTP 24 [erts-12.3.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Interactive Elixir (1.14.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Champions.Repo.all(Champions.Ranking.Match)
[debug] QUERY OK source="matches" db=23.5ms decode=1.3ms queue=15.4ms idle=717.2ms
SELECT m0."id", m0."result", m0."user_a_id", m0."user_b_id", m0."inserted_at", m0."updated_at" FROM "matches" AS m0 []
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
[
  %Champions.Ranking.Match{
    __meta__: #Ecto.Schema.Metadata<:loaded, "matches">,
    id: 1,
    result: :draw,
    user_a_id: 1,
    user_b_id: 2,
    inserted_at: ~N[2023-07-02 14:43:08],
    updated_at: ~N[2023-07-02 14:43:08]
  },
  %Champions.Ranking.Match{}
  ...
]
```

The trick is on line 5: `Champions.Repo.all(Champions.Ranking.Match)`. I've just used our `Repo.all` on our `Ranking.Match` model to see the results. But right now this is obnoxious to read, let's alias those:

```elixir
iex(2)> alias Champions.Repo
Champions.Repo
iex(3)> alias Champions.Ranking.Match
Champions.Ranking.Match
iex(4)> Repo.all(Match)
[debug] QUERY OK source="matches" db=23.5ms queue=0.1ms idle=1778.9ms
SELECT m0."id", m0."result", m0."user_a_id", m0."user_b_id", m0."inserted_at", m0."updated_at" FROM "matches" AS m0 []
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
```

Now it's more nice right? But what if you wanted to see the lastest ones? I'll explain those functions in detail later but you can copy this so you can reuse it later when you need:

```elixir
iex(5)> import Ecto.Query
Ecto.Query
iex(6)> Match |> order_by(desc: :id) |> limit(3) |> Repo.all
[debug] QUERY OK source="matches" db=22.0ms queue=18.3ms idle=1938.1ms
SELECT m0."id", m0."result", m0."user_a_id", m0."user_b_id", m0."inserted_at", m0."updated_at" FROM "matches" AS m0 ORDER BY m0."id" DESC LIMIT 3 []
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
[
  %Champions.Ranking.Match{
    __meta__: #Ecto.Schema.Metadata<:loaded, "matches">,
    id: 31,
    result: :draw,
    user_a_id: 1,
    user_b_id: 2,
    inserted_at: ~N[2023-07-02 18:29:05],
    updated_at: ~N[2023-07-02 18:29:05]
  },
  %Champions.Ranking.Match{
    __meta__: #Ecto.Schema.Metadata<:loaded, "matches">,
    id: 30,
    result: :draw,
    user_a_id: 1,
    user_b_id: 2,
    inserted_at: ~N[2023-07-02 18:29:04],
    updated_at: ~N[2023-07-02 18:29:04]
  },
  %Champions.Ranking.Match{
    __meta__: #Ecto.Schema.Metadata<:loaded, "matches">,
    id: 29,
    result: :draw,
    user_a_id: 1,
    user_b_id: 2,
    inserted_at: ~N[2023-07-02 18:29:04],
    updated_at: ~N[2023-07-02 18:29:04]
  }
]
```

We've imported `Ecto.query` to get functions like `order_by` and `limit` and just piped our query into Repo.all. Feel free to use it whenever you need.

## Where should functions related to points live?

As mentioned previously, these functions were added to the `Accounts` context for the sake of simplicity and since points are stored in the `User` model which is managed by `Accounts`. There's no exact answer here but my gut say that for this project the `Ranking` module should take care and know about everything related to points so I'm making a choice of moving those.

```diff
# lib/champions/ranking.ex
defmodule Champions.Ranking do
  @moduledoc """
  The Ranking context.
  """

  import Ecto.Query, warn: false
  alias Champions.Repo

+ alias Champions.Accounts
  alias Champions.Ranking.Match
+ alias Champions.Accounts.User

  @doc """
  Creates a match.

  ## Examples

      iex> create_match(%{field: value})
      {:ok, %Match{}}

      iex> create_match(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_match(attrs \\ %{}) do
    %Match{}
    |> Match.changeset(attrs)
    |> Repo.insert()
  end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking user changes.
+
+ ## Examples
+
+     iex> change_user_points(user)
+     %Ecto.Changeset{data: %User{}}
+
+ """
+ def change_user_points(%User{} = user, attrs \\ %{}) do
+   User.points_changeset(user, attrs)
+ end
+
+ @doc """
+ Updates the current number of points of a user
+
+ ## Examples
++     iex> update_user_points(user, 10)
+     {:ok, %User{points: 10}}
+
+ """
+ def update_user_points(%User{} = user, points) do
+   user
+   |> change_user_points(%{"points" => points})
+   |> Repo.update()
+ end
+
+ @doc """
+ Adds 3 points to the winning user
+
+ ## Examples
++     iex> concede_loss_to(%User{points: 0})
+     {:ok, %User{points: 3}}
+
+ """
+ def concede_loss_to(loser, winner) do
+   {:ok, _match} = create_match(%{
+     user_a_id: loser.id,
+     user_b_id: winner.id,
+     result: :winner_b
+   })
+   increment_user_points(winner, 3)
+ end
+
+ @doc """
+ Adds 1 point to each user
+
+ ## Examples
+
+     iex> declare_draw_match(%User{points: 0}, %User{points: 0})
+     {:ok, %User{points: 1}, %User{points: 1}}
+
+ """
+ def declare_draw_match(user_a, user_b) do
+   {:ok, _match} = create_match(%{
+     user_a_id: user_a.id,
+     user_b_id: user_b.id,
+     result: :draw
+   })
+   {:ok, updated_user_a} = increment_user_points(user_a, 1)
+   {:ok, updated_user_b} = increment_user_points(user_b, 1)
+   {:ok, updated_user_a, updated_user_b}
+ end
+
+ @doc """
+ Increments `amount` points to the user and returns its updated model
+
+ ## Examples
+
+     iex> increment_user_points(%User{points: 0}, 1)
+     {:ok, %User{points: 1}}
+
+ """
+ def increment_user_points(user, amount) do
+   {1, nil} =
+     User
+     |> where(id: ^user.id)
+     |> Repo.update_all(inc: [points: amount])
+
+   {:ok, Accounts.get_user!(user.id)}
+ end
end
```

There's a lot of additions plus note one function from `Accounts` is not there, `get_user!` so we aliased that model and changed the code to be `Accounts.get_user!`. Similarly we need to remove those from `Accounts`. Just remove those functions from there:

```diff
# lib/champions/accounts.ex
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Champions.Repo
- alias Champions.Ranking

# a ton of code

- @doc """
- Returns an `%Ecto.Changeset{}` for tracking user changes.
-
- ## Examples
-
-     iex> change_user_points(user)
-     %Ecto.Changeset{data: %User{}}
-
- """
- def change_user_points(%User{} = user, attrs \\ %{}) do
-   User.points_changeset(user, attrs)
- end
-
- @doc """
- Updates the current number of points of a user
-
- ## Examples
-
-     iex> update_user_points(user, 10)
-     {:ok, %User{points: 10}}
-
- """
- def update_user_points(%User{} = user, points) do
-   user
-   |> change_user_points(%{"points" => points})
-   |> Repo.update()
- end
-
- @doc """
- Adds 3 points to the winning user
-
- ## Examples
-
-     iex> concede_loss_to(%User{points: 0})
-     {:ok, %User{points: 3}}
-
- """
- def concede_loss_to(loser, winner) do
-   {:ok, _match} = Ranking.create_match(%{
-     user_a_id: loser.id,
-     user_b_id: winner.id,
-     result: :winner_b
-   })
-   increment_user_points(winner, 3)
- end
-
- @doc """
- Adds 1 point to each user
-
- ## Examples
-
-     iex> declare_draw_match(%User{points: 0}, %User{points: 0})
-     {:ok, %User{points: 1}, %User{points: 1}}
-
- """
- def declare_draw_match(user_a, user_b) do
-   {:ok, _match} = Ranking.create_match(%{
-     user_a_id: user_a.id,
-     user_b_id: user_b.id,
-     result: :draw
-   })
-   {:ok, updated_user_a} = increment_user_points(user_a, 1)
-   {:ok, updated_user_b} = increment_user_points(user_b, 1)
-   {:ok, updated_user_a, updated_user_b}
- end
-
- @doc """
- Increments `amount` points to the user and returns its updated model
-
- ## Examples
-
-     iex> increment_user_points(%User{points: 0}, 1)
-     {:ok, %User{points: 1}}
-
- """
- def increment_user_points(user, amount) do
-   {1, nil} =
-     User
-     |> where(id: ^user.id)
-     |> Repo.update_all(inc: [points: amount])
-
-   {:ok, get_user!(user.id)}
- end
```

Don't forget to remove the `alias Champions.Ranking` so you wont be seeing warnings in your terminal. Time to do a similar thing with tests. We will use this as an opportunity to get started on our `Ranking` tests.

```elixir
# test/champions/ranking_test.exs
defmodule Champions.RankingTest do
  use Champions.DataCase

  alias Champions.Ranking
  alias Champions.Accounts.User
  import Champions.AccountsFixtures

  describe "change_user_points/2" do
    test "accepts non-negative integers" do
      assert %Ecto.Changeset{} = changeset = Ranking.change_user_points(%User{}, %{"points" => -1})
      refute changeset.valid?

      assert %Ecto.Changeset{} = changeset = Ranking.change_user_points(%User{}, %{"points" => 0})
      assert changeset.valid?

      assert %Ecto.Changeset{} = changeset = Ranking.change_user_points(%User{}, %{"points" => 10})
      assert changeset.valid?
    end
  end

  describe "set_user_points/2" do
    setup do
      %{user: user_fixture()}
    end

    test "updates the amounts of points of an existing user", %{user: user} do
      {:ok, updated_user} = Ranking.update_user_points(user, 10)
      assert updated_user.points == 10
    end
  end

  describe "concede_loss_to/2" do
    test "adds 3 points to the winner" do
      loser = user_fixture()
      user = user_fixture()
      assert user.points == 0
      assert {:ok, %User{points: 3}} = Ranking.concede_loss_to(loser, user)
    end
  end

  describe "declare_draw_match/2" do
    test "adds 1 point to each user" do
      user_a = user_fixture()
      user_b = user_fixture()
      assert user_a.points == 0
      assert user_b.points == 0
      assert {:ok, %User{points: 1}, %User{points: 1}} = Ranking.declare_draw_match(user_a, user_b)
    end
  end

  describe "increment_user_points/2" do
    test "performs an atomic increment on a single user points amount" do
      user = user_fixture()
      assert user.points == 0
      assert {:ok, %User{points: 10}} = Ranking.increment_user_points(user, 10)
      assert {:ok, %User{points: 5}} = Ranking.update_user_points(user, 5)
      assert {:ok, %User{points: 15}} = Ranking.increment_user_points(user, 10)
    end
  end
end
```

We pretty much copy-pasted	 the functions from `Account` tests and renamed the context name. Feel free to copy the code above under `test/champions/ranking_test.exs` or challenge yourself to create the file and copy your current `test/champions/accounts_test.exs` and fix the differences, that's going to be interesting. It's worth mentioning your LiveView will be erroring right now so to focus only on this file run `mix test test/champions/ranking_test.exs`.

```diff
defmodule Champions.AccountsTest do
# a lot of code
- describe "change_user_points/2" do
-   test "accepts non-negative integers" do
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" => -1})
-     refute changeset.valid?
-
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" => 0})
-     assert changeset.valid?
-
-     assert %Ecto.Changeset{} = changeset = Accounts.change_user_points(%User{}, %{"points" => 10})
-     assert changeset.valid?
-   end
- end
-
- describe "set_user_points/2" do
-   setup do
-     %{user: user_fixture()}
-   end
-
-   test "updates the amounts of points of an existing user", %{user: user} do
-     {:ok, updated_user} = Accounts.update_user_points(user, 10)
-     assert updated_user.points == 10
-   end
- end
  describe "list_users/0" do
    test "show all users on our system" do
      user = user_fixture()
      assert [^user] = Accounts.list_users()
    end
  end
-
- describe "concede_loss_to/2" do
-   test "adds 3 points to the winner" do
-     loser = user_fixture()
-     user = user_fixture()
-     assert user.points == 0
-     assert {:ok, %User{points: 3}} = Accounts.concede_loss_to(loser, user)
-   end
- end
-
- describe "declare_draw_match/2" do
-   test "adds 1 point to each user" do
-     user_a = user_fixture()
-     user_b = user_fixture()
-     assert user_a.points == 0
-     assert user_b.points == 0
-     assert {:ok, %User{points: 1}, %User{points: 1}} = Accounts.declare_draw_match(user_a, user_b)
-   end
- end
-
- describe "increment_user_points/2" do
-   test "performs an atomic increment on a single user points amount" do
-     user = user_fixture()
-     assert user.points == 0
-     assert {:ok, %User{points: 10}} = Accounts.increment_user_points(user, 10)
-     assert {:ok, %User{points: 5}} = Accounts.update_user_points(user, 5)
-     assert {:ok, %User{points: 15}} = Accounts.increment_user_points(user, 10)
-   end
- end
end
```

I haven't realized before by `list_users/0` test was in the middle of that mess but it doesn't matter now.

```diff
defmodule ChampionsWeb.UserLive.Show do
# some code
  def handle_event("concede_loss", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
-   {:ok, updated_user} = Accounts.concede_loss_to(current_user, user)
+   {:ok, updated_user} = Ranking.concede_loss_to(current_user, user)
    {:noreply, assign(socket, :user, updated_user)}
  end

  def handle_event("concede_draw", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
-   {:ok, updated_my_user, updated_user} = Accounts.declare_draw_match(current_user, user)
+   {:ok, updated_my_user, updated_user} = Ranking.declare_draw_match(current_user, user)
    {:noreply,
      socket
      |> assign(:user, updated_user)
      |> assign(:current_user, updated_my_user)
    }
  end
```

There's no tricky here, this fixes our LiveView and consequently all our tests. Your `mix test` should be all green now!

## Testing the new things

We so far only focused on refactoring code and got some tests on `ranking_tests.exs` but those are all for old things, we need to ensure the new code works and will keep working. The easiest test we can do is just check if we can create a match:

```diff
defmodule Champions.RankingTest do
  use Champions.DataCase

  alias Champions.Ranking
+ alias Champions.Ranking.Match
  alias Champions.Accounts.User
  import Champions.AccountsFixtures

+ describe "create_match/1" do
+   test "create_match/1 with valid data creates a match" do
+     loser = user_fixture()
+     winner = user_fixture()
+     valid_attrs = %{result: :winner_a, user_a_id: loser.id, user_b_id: winner.id}
+
+     assert {:ok, %Match{} = match} = Ranking.create_match(valid_attrs)
+     assert match.result == :winner_a
+   end
+
+   test "create_match/1 with an invalid user ID fails" do
+     loser = user_fixture()
+     valid_attrs = %{result: :winner_a, user_a_id: loser.id, user_b_id: 10_000_000}
+
+     assert {:error, %Ecto.Changeset{} = changeset} = Ranking.create_match(valid_attrs)
+     assert {"does not exist", _rest} = Keyword.fetch!(changeset.errors, :user_b_id)
+   end
+ end
# more code
end
```

Now let's talk about the functions that generate matches: `Ranking.concede_loss_to/2` and `Ranking.declare_draw_match/2`. Their signature is to always return the users that were updated so our LiveView can update them. Doesn't seems we have a reason to change those functions specifically now so we need a different way to test that matches where created. Since Mix tests use a [sandboxed Postgres adapter](https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html) we can simply trust that the last Match will be the one just created.

```diff
  describe "concede_loss_to/2" do
    test "adds 3 points to the winner" do
      loser = user_fixture()
      user = user_fixture()
      assert user.points == 0
      assert {:ok, %User{points: 3}} = Ranking.concede_loss_to(loser, user)
+     match = get_last_match!()
+     assert match.user_a_id == loser.id
+     assert match.user_b_id == user.id
+     assert match.result == :winner_b
    end
  end

  describe "declare_draw_match/2" do
    test "adds 1 point to each user" do
      user_a = user_fixture()
      user_b = user_fixture()
      assert user_a.points == 0
      assert user_b.points == 0
      assert {:ok, %User{points: 1}, %User{points: 1}} = Ranking.declare_draw_match(user_a, user_b)
+     match = get_last_match!()
+     assert match.user_a_id == user_a.id
+     assert match.user_b_id == user_b.id
+     assert match.result == :draw
    end
  end

# put this in the end:

+ def get_last_match!() do
+   Match
+   |> order_by(desc: :id)
+   |> limit(1)
+   |> Repo.one!()
+ end
end
```

We created a simple `get_last_match!` helper function so we wont duplicate code here.  After getting the last match all we need is to check if the data is set according to our test and we are good. But now, assuming you never did an [Ecto.Query](https://hexdocs.pm/ecto/Ecto.Query.html) before, you could be confused about that and that's already the second time in this post I use it so let's talk about that.

## A 3 minutes introduction to Ecto.Query

Whenever we need to query data from our database, in the end, we use `Repo` with functions such as [Repo.exists?/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:exists?/2) to check if the query contains any results, [Repo.all/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2) to get all results that fit the query or [Repo.one/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:one/2) that will get a single result and error if there's none or more than one possible result. What those functions don't do is tell "what am I looking for". `Ecto.Query` and `Ecto.Repo` combine themselves to be more powerful: `Ecto.Query` scope things and `Ecto.Repo` executes the database call.

What's the most basic Ecto.Query possible? The answer is simple and you've been seeing me do that all the time: all `Ecto.Model` are queries. If you go to `iex -S mix` and do `Repo.all(Match)` (don't forget the aliases) you will see a list of all queries. So `Ecto.Model` is a query with no boundaries.

What if I want to get more specific results? That's were [Ecto.Query](https://hexdocs.pm/ecto/Ecto.Query.html) jumps in. It comes with very useful functions. They're so useful that, I don't know if you paid attention, all contexts contain `import Ecto.Query, warn: false` at the top because we all know we will be using them at some point so we ignore the warn until them, Phoenix will generate that for you in every single context. If you don't import it's also fine but you'll quickly see yourself writing things like `Ecto.Query.where` and `Ecto.Query.order_by`a lot so why not import as soon as needed.

Now how we scope things down with queries? We first start with a query that knows no boundaries: a model. After that we just keep adding constraints. Let's dive down into `get_last_match!/0` so we have an example.

```elixir
  def get_last_match!() do
# The query starts with `Match` so it reads as 'give me all matches'
# or if you like SQL: `select * from matches`
    Match
# As soon as we use order_by/3 we add a new constraint saying exactly what the
# function name days. SQL: `select * from matches order by id desc`
    |> order_by(desc: :id)
# Needless to say, limit/3 does the same.
# SQL: `select * from matches order by id desc limit 1`
    |> limit(1)
# As for Repo.one!/2 it will run the query and error if the count is not
# exactly 1. In this case, it only can be 0 or 1 because of our limit.
    |> Repo.one!()
  end
```

## Summary

- We learned how to create a new Ecto model from scratch, including migrations and why those two correlate.
- We created our first context by hand and reasoned about when functions should be moved from one to another
- We quickly glanced at how to see data on IEx also using `Ecto.Query`
- We also learned that Postgres tests live on a sandbox environment so things like getting the last row can be relied on for tests and we also created a helper function for our tests
- We quickly talked about how Ecto queries work scoping things down.

To prevent this post from becoming much longer I will stop it here and next time we will learn interesting stuff related to how to show data on LiveView and some Ecto tricks to make getting related models easier. See you next time.