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

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

## Adding points to users

In the previous chapter, we just added the points column to `users` table. We need a way to add points to users. This time we are going to learn how to use Ecto to modify rows in our database.

## Ecto Changesets 101

Ecto comes with a powerful abstraction to validate and map data from user input to the database, it's called [Ecto.Changeset](https://hexdocs.pm/ecto/Ecto.Changeset.html). Think of it as an intermediate state between user input and actually storing the data.

![Words "Model+User input" with a arrow pointing to "Changet" with another arrow pointing to "Stored in Database"](https://slabstatic.com/prod/uploads/or5adfz5/posts/images/3UMQmIMv1yKOyZzmWePbFS2H.png)

User inputs usually come in the form of an Elixir map (a key-value structure) such as `%{"points" => 10}`. Since we want to make changes, we must change something. If you wanted to update an existing user, you first need to get that user then you'd have a variable with a value such as `%User{email: "…", …}`. Combine both with a `change_*` function and we have a changeset. Let's do it from scratch!

Since we are going to make a function that modifies our users it makes sense to use the `accounts.ex` context and since this is related to our Ecto model we will also be modifying our `user.ex` file too. With the same reasoning that means we will be testing this at `accounts_test.exs`.

Head out to `lib/champions/accounts/user.ex`. You'll notice there are multiple `*_changeset` named functions with specific intents. Scroll to the bottom and create a new function called `points_changeset/2`.

```elixir
@doc """
A user changeset to update points
"""
def points_changeset(user, attrs) do
  user
  |> cast(attrs, [:points])
  |> validate_required([:points])
  |> validate_number(:points, greater_than_or_equal_to: 0)
end
```

There's a lot to unpack so let's go bit by bit. We use `@doc` to give this function a nice documentation for the developers of the future who want to use this. Following that we define a function that takes two arguments: the user to be modified and the new data on the `attrs` variable.

The fun comes with the pipes. It's all about transformation. On line 5 we start with the user as-is. On line 6 we use [cast/4](https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4) to convert the user into a changeset but we limit the `attrs` to only read `points` changes and ignore everything else. Next, we do two [validations](https://hexdocs.pm/ecto/Ecto.Changeset.html#summary): we want to make `points` mandatory and it should be a non-negative integer. By the end of this pipe chain, we have a validated changeset without any trouble at all.

Then, we need to expose this function to our Accounts context. Head out to `lib/champions/accounts.ex` and create a new function in the end called `change_user_points/2`.

```elixir
@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
```

As you can see this is just an alias to the changeset contained in the Ecto model. Making your context module the place to talk to the external world (your_app_web) is a good practice. Last but not least, we need to test that this actually works. go to `test/champions/accounts_test.exs` and add a new test case in the end:

```elixir
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
```

With this test, we pass an empty user and a negative, zero, and positive amount of points. Note that at all times it returns an Ecto.Changeset, what changes is that if it's invalid the changeset `valid?` property will be false.

## Applying changesets using Repo

Whenever an Ecto user wants to apply changes to the database they must go through a Repo. Think of those as abstractions between Elixir and the database itself. You don't need to handle creating connections, recovering from disconnections, writing SQL, and many other things because Repo will take care of that for you alongside your changeset. Since Phoenix by default comes with Postgres support you should have a `Champions.Repo` module ready to be used at any time, otherwise not even the migrations would have worked.

Let's create a high-level function on `Accounts` that takes a user and a number of points and updated the user points. Inside `accounts.ex` create a new function `update_user_points/2`:

```elixir
@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
```

The only new thing here is that there's a pipe after the changeset that calls [Repo.update/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:update/2) to trigger the changes to the database. In summary, the changeset works as a mapping of what needs to be done in the database and Repo actually applies those changes. To test this out add a new unit test to `accounts_test.exs`:

```elixir
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
```

A cool thing worth mentioning here is that out is that the Phoenix auth generator also created the helper `user_fixture/1` function that creates a user with zero trouble so your unit tests can be DRY.

## Summary

- To store data in the database Ecto uses changesets.
- Changesets validate and prepare which data can go to the database, you can even have more than one per Ecto model. Ecto.Changeset comes with useful validation functions.
- Repo is how Ecto talks to the database.
- [Repo.update/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:update/2) reads a changeset and applies changes to the database.

[Read chapter 5: Listing our users](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-5-5cn3)