Modifier la base de données du blog Phoenix

Le blog créé avec Phoenix utilise une base de données PostgreSQL. Cette base de données contient la table users, et nous souhaitons modifier la base de données du blog Phoenix afin d’ajouter la colonne nom à la table users.

Ainsi, il sera possible de compléter les informations sur les articles avec le nom de l’auteur. Cet article complète la création du blog et les trois articles précédents :

Cet article vient aussi compléter nos articles sur Ecto et PostgreSQL :

La migration Ecto

L’utilisation de la base de données PostgreSQL avec Phoenix se simplifie en utilisant Ecto. Ecto ajoute le code nécessaire pour accéder à la base de données PostgreSQL depuis Elixir et Phoenix. Pour modifier un schéma de base de données, Ecto propose de créer des modules appelés Migration. Une migration est un code qui modifie le schéma de la base de données.

Modifier le schema du Module Ecto relié à la base de données du blog Phoenix

Ecto permet de créer un schema qui se connecte à la base de données. Nous devons avoir une correspondance entre le schema et la table de données. Donc la première étape est de créer le champ dans le schéma correspondant à la colonne que nous souhaitons ajouter.

Nous souhaitons ajouter le nom :name.

BLOGSET/lib/blogset/accounts/user.ex :

defmodule Blogset.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime

    timestamps(type: :utc_datetime)
  end
  ...

Nous avons ajouté la ligne field :name, :string en ligne 6.

Ajouter une migration pour modifier la table dans la base de données du blog Phoenix

Nous allons montrer comment ajouter la colonne nom à la table des utilisateur users. La première étape est de générer le fichier de migration en lui donnant un libellé. Comme l’objectif est de pouvoir identifier chaque migration par son rôle, nous allons appeler notre migration add_users_name.

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
  • mix ecto.gen.migration add_users_name ‘créer le fichier de migration add_users_name

L’exécution de ce générateur va créer un fichier dans lequel nous devons coder les changements à faire dans la base de données. Le fichier exs généré aura un nom avec un code horodaté suivi du nom que nous avons choisi, ici add_users_name.

C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.gen.migration add_users_name
* creating priv/repo/migrations/20240110132107_add_users_name.exs

C:\CarbonX1\Phoenix\Projets\blogset>

Le fichier BLOGSET/priv/repo//migrations/20240110132107_add_users_name.exs :

defmodule Blogset.Repo.Migrations.AddUsersName do
  use Ecto.Migration

  def change do

  end
end

Nous devons modifier le fichier pour qu’il produise les changements attendus :

defmodule Blogset.Repo.Migrations.AddUsersName do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :name, :string
    end
  end
end

Maintenant que ce fichier de migration est créé, nous pouvons lancer son execution avec :

  • mix ecto.migrate

Cette commande lance en exécution tous les fichiers de migration non encore exécuté.

C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.migrate

14:38:39.679 [info] == Running 20240110132107 Blogset.Repo.Migrations.AddUsersName.change/0 forward

14:38:39.681 [info] alter table users

14:38:39.697 [info] == Migrated 20240110132107 in 0.0s

C:\CarbonX1\Phoenix\Projets\blogset>

Nous voyons dans VS Code la liste de toutes les migrations :

Modifier la base de données du blog Phoenix#1 - Les migrations créées par Ecto pour le projet blogset
Modifier la base de données du blog Phoenix#1 – Les migrations créées par Ecto pour le projet blogset

L’ensemble des changements possibles avec la migration Ecto est expliqué dans la documentation d’Ecto.

Vérification de la modification de la base de données

Regardons maintenant notre table users :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
  • psql -U postgres ‘se connecter en tant qu’utilisateur username: postgres
  • \c ‘pour vérifier la connexion
  • \c blogset_dev ‘ pour se connecter à la base blogset_dev
  • \d ‘ liste les tables de la base blogset_dev
  • \d users ‘ liste les colonnes de la table users
  • select * from users; ‘liste les données de la table users
  • \q ‘pour quitter postgres

Nous voyons maintenant notre table avec la colonne name :

C:\CarbonX1\Phoenix\Projets\blogset>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
Attention : l'encodage console (850) diffère de l'encodage Windows (1252).
            Les caractères 8 bits peuvent ne pas fonctionner correctement.
            Voir la section « Notes aux utilisateurs de Windows » de la page
            référence de psql pour les détails.
Saisissez « help » pour l'aide.

postgres=# \c blogset_dev
Vous êtes maintenant connecté à la base de données « blogset_dev » en tant qu'utilisateur « postgres ».
blogset_dev=# \d users
                                               Table ½ public.users ╗
     Colonne     |              Type              | Collationnement | NULL-able |            Par dÚfaut
-----------------+--------------------------------+-----------------+-----------+-----------------------------------
 id              | bigint                         |                 | not null  | nextval('users_id_seq'::regclass)
 email           | citext                         |                 | not null  |
 hashed_password | character varying(255)         |                 | not null  |
 confirmed_at    | timestamp(0) without time zone |                 |           |
 inserted_at     | timestamp(0) without time zone |                 | not null  |
 updated_at      | timestamp(0) without time zone |                 | not null  |
 name            | character varying(255)         |                 |           |
Index :
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_index" UNIQUE, btree (email)
RÚfÚrencÚ par :
    TABLE "stories" CONSTRAINT "stories_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
    TABLE "users_tokens" CONSTRAINT "users_tokens_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE


blogset_dev=# select * from users;
 id |      email       |                                                           hashed_password                                                           | confirmed_at |     inserted_at     |     updated_at      | name
----+------------------+-------------------------------------------------------------------------------------------------------------------------------------+--------------+---------------------+---------------------+------
  1 | test@gmail.com   | $pbkdf2-sha512$160000$biq8ifwX27Kv5qvBNOGjpg$BbsIO29Y3eLO9mh1iY4.jtnX/QmEcvTHTk1v2JGHFsvzQyXx6yfMNiqtvkNp8jK6luSKZL0PDiOSwUuG0oLMkg |              | 2024-01-03 10:33:52 | 2024-01-03 10:33:52 |
  2 | auteur@gmail.com | $pbkdf2-sha512$160000$6rXWpfI8KYt8HRZjZmh8Iw$ELz2fql37bC9M4GsluwfsvyCuWWgPK/Ok286fb.uxAfLtI4YejPzQG87x7rRT3cKT6LBbdFCV61.1pPok9kabQ |              | 2024-01-03 14:29:25 | 2024-01-03 14:29:25 |
(2 lignes)


blogset_dev=# \q

C:\CarbonX1\Phoenix\Projets\blogset>

Bien sûr, les donnnées de la colonne name sont pour l’instant vide pour chacun des utilisateurs déjà créés.

Modification de l’application Phoenix

Maintenant que nous avons ajouté la clolonne name, nous pouvons modifier l’application pour l’utiliser dans notre blog.

Pour activer blogset :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘dossier du projet
  • iex -S mix phx.server ‘execution en mode interactif
  • http://localhost:4000/ ‘page d’accueil

Modification du formulaire d’enregistrement des utilisateurs

L’enregistrement des utilisateurs se fait par le bouton Register sur la page d’accueil.

Modifier la base de données du blog Phoenix#2 - Le formulaire de création de compte
Modifier la base de données du blog Phoenix#2 – Le formulaire de création de compte

Nous pouvons voir l’url utilisé pour accéder à cette page : http://localhost:4000/users/register

Si nous allons dans le router, nous voyons la ligne 6 ci-dessous définissant la route vers la page users/register.

  scope "/", BlogsetWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]

    live_session :redirect_if_user_is_authenticated,
      on_mount: [{BlogsetWeb.UserAuth, :redirect_if_user_is_authenticated}] do
      live "/users/register", UserRegistrationLive, :new
      live "/users/log_in", UserLoginLive, :new
      live "/users/reset_password", UserForgotPasswordLive, :new
      live "/users/reset_password/:token", UserResetPasswordLive, :edit
    end

    post "/users/log_in", UserSessionController, :create
  end

La ligne 6 montre que cette page passe par UserRegistrationLive, :new :

Pour trouver UserRegistrationLive, nous utilisons la loupe de VS Code.

Modifier la base de données du blog Phoenix#3 - Recherche de UserRegistrationLive dans VS Code
Modifier la base de données du blog Phoenix#3 – Recherche de UserRegistrationLive dans VS Code

Nous devons mainteant modifier le html du render de UserRegistrationLive pour ajouter dans le formulaire le :name.

  • <.input field={@form[:name]} type= »text » label= »Name » required /> ‘ajouté ligne 28

BLOGSET/lib/blogset_web/live/user_registration_live.ex :

  def render(assigns) do
    ~H"""
    <div class="mx-auto max-w-sm">
      <.header class="text-center">
        Register for an account
        <:subtitle>
          Already registered?
          <.link navigate={~p"/users/log_in"} class="font-semibold text-brand hover:underline">
            Sign in
          </.link>
          to your account now.
        </:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="registration_form"
        phx-submit="save"
        phx-change="validate"
        phx-trigger-action={@trigger_submit}
        action={~p"/users/log_in?_action=registered"}
        method="post"
      >
        <.error :if={@check_errors}>
          Oops, something went wrong! Please check the errors below.
        </.error>

        <.input field={@form[:name]} type="text" label="Name" required />
        <.input field={@form[:email]} type="email" label="Email" required />
        <.input field={@form[:password]} type="password" label="Password" required />

        <:actions>
          <.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

Nous pouvons maintenant aller sur la page et voir le nouveau formulaire. Dès que la modification de render dans le module est enregistrée, la page se met à jour si le serveur est actif et la page visiblle dans un navigateur.

Modifier la base de données du blog Phoenix#4 - Le formulaire de création de compte avec le nom
Modifier la base de données du blog Phoenix#4 – Le formulaire de création de compte avec le nom

Modification du module afin d’enregistrer le nouveau champ dans la base

Inutile d’enregistrer un nouvel utilisateur pour l’instant. Nous devons modifier user.ex pour que la valeur de name soit prise en compte par Ecto, puis envoyer dans la base de données. Nous ajoutons dans la fonction registration_changeset de user.ex dans cast la valeur :name correspondant au nom de la colonne, afin que le nom soit enregistré dans la base (ligne 3).

BLOGSET/lib/blogset/accounts/user.ex :

  def registration_changeset(user, attrs, opts \\ []) do
    user
    |> cast(attrs, [:email, :password, :name])
    |> validate_email(opts)
    |> validate_password(opts)
  end

Comment savons nous qu’il faut modifier la fonction registration_changeset dans user.ex ?

En fait, si nous allons dans la fonction render de UserRegistrationLive présenté précédemment, nous avons ligne 18 : phx-submit= »save ». Et dans le même module UserRegistrationLive nous avons la fonction handle_event(« save », _ , _ ) qui traite le save :

BLOGSET/lib/blogset_web/live/user_registration_live.ex :

  def handle_event("save", %{"user" => user_params}, socket) do
    case Accounts.register_user(user_params) do
      {:ok, user} ->
        {:ok, _} =
          Accounts.deliver_user_confirmation_instructions(
            user,
            &url(~p"/users/confirm/#{&1}")
          )

        changeset = Accounts.change_user_registration(user)
        {:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, socket |> assign(check_errors: true) |> assign_form(changeset)}
    end
  end

En ligne 2 nous voyons case Accounts.register_user(user_params) do, qui essaye d’enregistrer le nouveau user et va permettre de définir comment traiter les différents cas d’erreur. Nous devons donc aller dans le module Accounts et trouver la fonction register_user/1.

BLOGSET/lib/blogset/accounts.ex :

  def register_user(attrs) do
    %User{}
    |> User.registration_changeset(attrs)
    |> Repo.insert()
  end

Et nous voyons que l’enregistrement se fait dans User.registration_changeset(attrs).

Vérification du changement dans la base de données

Lançons le serveur si ce n’est pas déjà fait :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
  • iex -S mix phx.server
  • http://localhost:4000

Et allons sur la page de création de compte avec le bouton Register. Saisissons un nouvel utilisateur et validons.

Modifier la base de données du blog Phoenix#5 - Création d'un nouvel utilisateur
Modifier la base de données du blog Phoenix#5 – Création d’un nouvel utilisateur

Retour de l’action Save :

Modifier la base de données du blog Phoenix#6 - La création du nouvel utilisateur est validée
Modifier la base de données du blog Phoenix#6 – La création du nouvel utilisateur est validée

Nous pouvons maintenant regarder le contenu de la base de données :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
  • psql -U postgres ‘se connecter en tant qu’utilisateur username: postgres
  • \c ‘pour vérifier la connexion
  • \c blogset_dev ‘ pour se connecter à la base blogset_dev
  • \d ‘ liste les tables de la base blogset_dev
  • \d users ‘ liste les colonnes de la table users
  • select * from users; ‘liste les données de la table users
  • \q ‘pour quitter postgres
C:\CarbonX1\Phoenix\Projets\blogset>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
Attention : l'encodage console (850) diffère de l'encodage Windows (1252).
            Les caractères 8 bits peuvent ne pas fonctionner correctement.
            Voir la section « Notes aux utilisateurs de Windows » de la page
            référence de psql pour les détails.
Saisissez « help » pour l'aide.

postgres=# \c blogset_dev
Vous êtes maintenant connecté à la base de données « blogset_dev » en tant qu'utilisateur « postgres ».

blogset_dev=# select * from users;
 id |           email           |                                                           hashed_password                                                           | confirmed_at |     inserted_at     |     updated_at      |      name
----+---------------------------+-------------------------------------------------------------------------------------------------------------------------------------+--------------+---------------------+---------------------+-----------------
  1 | test@gmail.com            | $pbkdf2-sha512$160000$biq8ifwX27Kv5qvBNOGjpg$BbsIO29Y3eLO9mh1iY4.jtnX/QmEcvTHTk1v2JGHFsvzQyXx6yfMNiqtvkNp8jK6luSKZL0PDiOSwUuG0oLMkg |              | 2024-01-03 10:33:52 | 2024-01-03 10:33:52 |
  2 | auteur@gmail.com          | $pbkdf2-sha512$160000$6rXWpfI8KYt8HRZjZmh8Iw$ELz2fql37bC9M4GsluwfsvyCuWWgPK/Ok286fb.uxAfLtI4YejPzQG87x7rRT3cKT6LBbdFCV61.1pPok9kabQ |              | 2024-01-03 14:29:25 | 2024-01-03 14:29:25 |
  4 | francois-martin@gmail.com | $pbkdf2-sha512$160000$o/GFFP307g0EbyAInDhRKA$OJK1dV6bzoMKqyD.J2254tSDPud1cBVit2LmH38XVm0L8h7nVcB4Y5sgF0svxf1MauStItEXSC1ZhFWs2EP.DA |              | 2024-01-10 15:34:29 | 2024-01-10 15:34:29 | Franþois MARTIN
(3 lignes)


blogset_dev=# \q

C:\CarbonX1\Phoenix\Projets\blogset>

Le nom de notre nouvel utilisateur est bien enregistré dans la base de données (ligne 18).

Pour supprimer un utilisateur nous avons la requête SQL :

  • DELETE FROM users WHERE id=3 ;

Lorsqu’un utilisateur est supprimé la clé défini dans la colonne id n’est pas impactée. Nous voyons bien ici qu’il n’y a plus d’utilisateur avec id=3.

Modifier la base de données du blog Phoenix, quel résultat ?

Nous avons bien modifié notre base de données ainsi que le module qui accède à la base de données. Le nouveau champ a été ajouté dans le formulaire LiveView. Nous pouvons maintenant utiliser le nom de l’utilisateur pour l’afficher dans les articles et c’est ce que nous alllons faire maintenant.

Lançons le serveur si ce n’est pas déjà fait :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
  • iex -S mix phx.server
  • http://localhost:4000

Utiliser le nom d’auteur dans la liste des articles

A chaque article nous avons l’identifiant de son auteur. Cela permet d’établir un lien entre l’article de la table stories et le rédacteur de l’article de la table users. Nous venons d’ajouter le nom dans la colonne name de la table users.

Dans la page d’accueil nous avons la liste d’articles. Cette liste provient de StoryLive.Home. Dans la partie render home.html.heex, nous pouvons ajouter la colonne name à la liste des items de la table <.table></.table> en ligne 12 :

  • <:col :let={{_id, story}} label= »Name »><%= story.user_id%></:col>

BLOGSET/lib/blogset_web/live/story_live/home.html.heex :

<.header>
  Liste de tous les articles
</.header>

<.table
  id="stories"
  rows={@streams.stories}
  row_click={fn {_id, story} -> JS.navigate(~p"/stories/#{story}") end}
>
  <:col :let={{_id, story}} label="Title"><%= story.title %></:col>
  <:col :let={{_id, story}} label="Body"><%= story.body %></:col>
  <:col :let={{_id, story}} label="Name"><%= story.user_id %></:col>
  <:action :let={{_id, story}}>

      <.link navigate={~p"/stories/#{story}"}>Show</.link>


  </:action>

</.table>

<.modal :if={@live_action in [:new, :edit]} id="story-modal" show on_cancel={JS.patch(~p"/stories")}>
  <.live_component
    module={BlogsetWeb.StoryLive.FormComponent}
    id={@story.id || :new}
    title={@page_title}
    action={@live_action}
    story={@story}
    user_id={@current_user.id}
    patch={~p"/stories"}
  />
</.modal>

Cela permet d’ajouter l’id de l’auteur pour chaque article. Nous avons dans story.ex la référence qui donne pour user_id , l’id de user (ligne 10).

BLOGSET/lib/blogset/stories/story.ex :

defmodule Blogset.Stories.Story do
  use Ecto.Schema
  import Ecto.Changeset

  require Logger

  schema "stories" do
    field :title, :string
    field :body, :string
    field :user_id, :id

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(story, attrs) do
    Logger.info(Blogset_Stories_Story_attrs: attrs)
    story
    |> cast(attrs, [:title, :body, :user_id])
    |> validate_required([:title, :body])
  end
end

La page d’accueil montre l’id de chaque article.

Modifier la base de données du blog Phoenix#8 - L'id de l'auteur s'affiche dans la page d'accueil
Modifier la base de données du blog Phoenix#8 – L’id de l’auteur s’affiche dans la page d’accueil

Maintenant nous souhaitons prendre le name de l’auteur à partir de son id.

nous allons à la place de définir dans BLOGSET/lib/blogset/stories/story.ex :

  • field : user_id, :id ‘avant (et à remplacer par la ligne ci-dessous)
  • belongs_to :user, Blogset.Accounts.User ‘après

Nous pouvons maintenant dans la page remplacer la colonne id par l’une des colonnes de la table users.

  • <:col :let={{_id, story}} label= »Name »><%= story.user.name%></:col>

Pour que cela fonctionne nous devons charger le user. Cela se fait dans stories.ex, lorsque nous utilisons list_stories, nous devons ajouter :

  • Repo.all(Story) |> Repo.preload(:user)

Lorsque les utilisateurs ont un nom de défini celui-ci s’affiche alors dans la colonne name pour la liste des stories de la page d’accueil.

Utiliser le nom de l’auteur dans la parti détail de l’article

Pour avoir le nom de l’auteur dans la partie détail de l’article, nous devons aussi ajouter

  • |> Repo.preload(:user)

dans la fonction get_story!(id) dans stories.ex

Mais aussi ajouter dans show.html.heex l’item

  • <:item title= »Name »><%= @story.user.name %></:item>

Conclusion pour « modifier la base de données du blog Phoenix »

Nous avons modifié le schéma de la base de données pour ajouter une colonne à la table user : la colonne name. Nous avons ensuite montré comment récupérer la valeur name de l’auteur de l’article aussi bien dans la liste des articles que dans la partie détail de l’article.

Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire