Ecto pour utiliser Postgres avec Phoenix

Découverte d’ Ecto pour utiliser Postgres avec Phoenix. Le cours sur Ecto proposé par Productive Programmer nous donne la trame de cet article.

Après avoir montré comment créer une application phoenix pas à pas. Nous allons maintenant utiliser Ecto pour ajouter une base de données Postgres à nos projets Phoenix.

Création du projet Customer

Nous créons le projet dans le dossier :

  • C:\CarbonX1\Phoenix\Projets

Vérifions l’installation de postgreSQL :

  • psql –version

Puis nous activons la commande de création du projet :

  • mix phx.new customer ‘customer est le nom du projet
C:\CarbonX1\Phoenix\Projets>psql --version
psql (PostgreSQL) 15.4

C:\CarbonX1\Phoenix\Projets>mix phx.new customer
* creating customer/config/config.exs
* creating customer/config/dev.exs
* creating customer/config/prod.exs
* creating customer/config/runtime.exs
* creating customer/config/test.exs
* creating customer/lib/customer/application.ex
* creating customer/lib/customer.ex
* creating customer/lib/customer_web/controllers/error_json.ex
* creating customer/lib/customer_web/endpoint.ex
* creating customer/lib/customer_web/router.ex
* creating customer/lib/customer_web/telemetry.ex
* creating customer/lib/customer_web.ex
* creating customer/mix.exs
* creating customer/README.md
* creating customer/.formatter.exs
* creating customer/.gitignore
* creating customer/test/support/conn_case.ex
* creating customer/test/test_helper.exs
* creating customer/test/customer_web/controllers/error_json_test.exs
* creating customer/lib/customer/repo.ex
* creating customer/priv/repo/migrations/.formatter.exs
* creating customer/priv/repo/seeds.exs
* creating customer/test/support/data_case.ex
* creating customer/lib/customer_web/controllers/error_html.ex
* creating customer/test/customer_web/controllers/error_html_test.exs
* creating customer/lib/customer_web/components/core_components.ex
* creating customer/lib/customer_web/controllers/page_controller.ex
* creating customer/lib/customer_web/controllers/page_html.ex
* creating customer/lib/customer_web/controllers/page_html/home.html.heex
* creating customer/test/customer_web/controllers/page_controller_test.exs
* creating customer/lib/customer_web/components/layouts/root.html.heex
* creating customer/lib/customer_web/components/layouts/app.html.heex
* creating customer/lib/customer_web/components/layouts.ex
* creating customer/priv/static/images/logo.svg
* creating customer/lib/customer/mailer.ex
* creating customer/lib/customer_web/gettext.ex
* creating customer/priv/gettext/en/LC_MESSAGES/errors.po
* creating customer/priv/gettext/errors.pot
* creating customer/priv/static/robots.txt
* creating customer/priv/static/favicon.ico
* creating customer/assets/js/app.js
* creating customer/assets/vendor/topbar.js
* creating customer/assets/css/app.css
* creating customer/assets/tailwind.config.js
* creating customer/assets/vendor/heroicons/LICENSE.md
* creating customer/assets/vendor/heroicons/UPGRADE.md
* extracting customer/assets/vendor/heroicons/optimized

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix assets.setup
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd customer

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server


C:\CarbonX1\Phoenix\Projets>

Nous suivons les instructions et nous rendant dans le dossier customer :

  • cd customer

Puis nous ouvrons VS Code avec :

  • code .

Les paramètres de connexion à la base de données sont notés dans dev.exs, fichier de configuration en mode developper :

  • CUSTOMER/config/dev.exs

Et nous voyons les valeurs choisies pour username et password :

# Configure your database
config :customer, Customer.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "customer_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

pour vérifier les paramètres de configuration de la base de données postgreSQL nous utilisons la commande en prenant « postgres » comme paramètre c’est à dire le username dans la configuration ci-dessus.

  • psql -U postgres ‘pour se connecter en tant qu’utilisateur username: postgres
  • \c ‘pour vérifier la connexion
  • \q ‘pour quitter postgres
C:\CarbonX1\Phoenix\Projets\customer>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (15.4)
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
Vous êtes maintenant connecté à la base de données « postgres » en tant qu'utilisateur « postgres ».
postgres=# \q

C:\CarbonX1\Phoenix\Projets\customer>

Nous avons maintenant vérifier que la configuration de postgreSQL est conforme à ce que nous avons indiqué pour notre projet Customer.

Si nous activons le projet directement, nous aurons une erreur car la base de données n’a pas été créée :

  • mix phx.server ‘activer le serveur
  • http://localhost:4000 ‘aller sur la page dans le navigateur
Créer un projet avec Ecto et Phoenix#1-Visualisation de la page html avant création de la base de données pour le projet
Créer un projet avec Ecto et Phoenix#1-Visualisation de la page html avant création de la base de données pour le projet

Ecto create pour utiliser la base de données Postgres avec Phoenix

La base de données doit être créée avec la commande :

  • mix ecto.create

Relançons le serveur puis allons sur la page :

  • iex -S mix phx.server ‘activation du serveur dans iex
  • http://localhost:4000 ‘activation de la page

Nous avons maintenant la page standard Phoenix :

Créer un projet avec Ecto et Phoenix#2-La page après création de la base de données du projet
Créer un projet avec Ecto et Phoenix#2-La page après création de la base de données du projet

Nous pouvons maintenant vérifier notre connexion à la base de données dans postgres en ligne de commande :

  • psql -U postgres ‘ pour ouvrir une session vers la base de données avec l’utilisateur postgres
  • \c customer_dev ‘ pour se connecter à la base customer_dev
  • \q ‘pour quitter psql
C:\CarbonX1\Phoenix\Projets\customer>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (15.4)
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 customer_dev
Vous êtes maintenant connecté à la base de données « customer_dev » en tant qu'utilisateur « postgres ».
customer_dev=# \q

C:\CarbonX1\Phoenix\Projets\customer>

La base de données cutomer_dev est créée mais nous n’avons encore ajouté aucune table.

Ecto, utiliser le schéma de données pour Postgres avec Phoenix

Ecto propose des outils pour générer le code Phoenix et aider à la création des changments dans la base de données.

Nous devons nous placer dans le dossier du projet :

  • cd C:\CarbonX1\Phoenix\Projets\Customer

Pour créer les colonnes de la table et la table, nous utilisons le générateur phx.gen.schema auquel nous donnons le nom du schema [Customer] (le module Phoenix pour l’accès à notre table) puis le nom de la table [customers] suivi des colonnes [first_name, last_name, email, age] avec pour chaque colonne son type de données [string ou integer] :

  • mix phx.gen.schema Customer customers first_name:string last_name:string email:string age:integer
C:\CarbonX1\Phoenix\Projets\customer>mix phx.gen.schema Customer customers first_name:string last_name:string email:string age:integer
* creating lib/customer/customer.ex
* creating priv/repo/migrations/20231123134225_create_customers.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate


C:\CarbonX1\Phoenix\Projets\customer>

Cette commande va créer 2 fichiers :

  • lib/cutomer/customer.ex ‘ le module Customer
  • priv/repo/migrations/20231123134225_create_customers.exs ‘le code de la migration.

Pour modifier le schema de la table Ecto propose de faire des migrations. Le fichier de migration est créé par la commande mix phx.gen.schema. Le fichier de migration a un numéro de migration lié à la date : 20231123134225.

Regardons les fichiers générés :

Le fichier avec le module Customer : lib/customer/customer.ex :

defmodule Customer.Customer do
  use Ecto.Schema
  import Ecto.Changeset

  schema "customers" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :age, :integer

    timestamps()
  end

  @doc false
  def changeset(customer, attrs) do
    customer
    |> cast(attrs, [:first_name, :last_name, :email, :age])
    |> validate_required([:first_name, :last_name, :email, :age])
  end
end

Le module Customer du projet Customer est nommé Customer.Customer.

priv/repo/migrations/20231123134225_create_customers.exs :

defmodule Customer.Repo.Migrations.CreateCustomers do
  use Ecto.Migration

  def change do
    create table(:customers) do
      add :first_name, :string
      add :last_name, :string
      add :email, :string
      add :age, :integer

      timestamps()
    end
  end
end

L’exécution de la migration se fait avec :

  • mix ecto.migrate ‘exécute toutes les migrations situées dans priv/repo/migrations/
C:\CarbonX1\Phoenix\Projets\customer>mix ecto.migrate
Compiling 1 file (.ex)
Generated customer app

14:45:09.653 [info] == Running 20231123134225 Customer.Repo.Migrations.CreateCustomers.change/0 forward

14:45:09.655 [info] create table customers

14:45:09.684 [info] == Migrated 20231123134225 in 0.0s

C:\CarbonX1\Phoenix\Projets\customer>

On peut vérifier dans PostgreSQL les changements qui ont eu lieu :

  • psql -U postgres ‘ se connecter à la base avec l’utilisateur postgres
  • \connect customer_dev ‘se connecter à notre base de données customer_dev
  • \d ‘ liste les tables de la base customer_dev
  • \d customers ‘ liste les colonnes de la table customers
  • \q ‘quitter psql
C:\CarbonX1\Phoenix\Projets\customer>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (15.4)
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=# \connect customer_dev
Vous êtes maintenant connecté à la base de données « customer_dev » en tant qu'utilisateur « postgres ».
customer_dev=# \d
                 Liste des relations
 SchÚma |        Nom        |   Type   | PropriÚtaire
--------+-------------------+----------+--------------
 public | customers         | table    | postgres
 public | customers_id_seq  | sÚquence | postgres
 public | schema_migrations | table    | postgres
(3 lignes)


customer_dev=# \d customers
                                             Table ½ public.customers ╗
   Colonne   |              Type              | Collationnement | NULL-able |              Par dÚfaut          
-------------+--------------------------------+-----------------+-----------+---------------------------------------
 id          | bigint                         |                 | not null  | nextval('customers_id_seq'::regclass)
 first_name  | character varying(255)         |                 |           |
 last_name   | character varying(255)         |                 |           |
 email       | character varying(255)         |                 |           |
 age         | integer                        |                 |           |
 inserted_at | timestamp(0) without time zone |                 | not null  |
 updated_at  | timestamp(0) without time zone |                 | not null  |
Index :
    "customers_pkey" PRIMARY KEY, btree (id)


customer_dev=# \q

C:\CarbonX1\Phoenix\Projets\customer>

On note que Phoenix a ajouté :

  • une colonne id:bigint qui est l’identifiant unique qui sera généré automatiquement pour chaque enregistrement dans la table.
  • deux colonnes timestamp : inserted_at et updated_at

Utiliser le changeset Ecto avec Postgres et Phoenix

Nous allons utiliser la version interactive d’Elixir avec iex. Nous ouvrons le terminal.

  • iex -S mix

pour simplifier l’écriture de code sans avoir à rappeler Customer.Customer devant les fonctions que nous allons utiliser, nous créons l’alias Customer.Customer :

  • alias Customer.Customer

Nous pouvons maintenant appeler la fonction changeset de Customer. Cette fonction reçoit le schéma sous la forme d’une structure Customer et les données qui sont les attributs.

@doc false
def changeset(customer, attrs) do
	customer
	|> cast(attrs, [:first_name, :last_name, :email, :age])
	|> validate_required([:first_name, :last_name, :email, :age])
end

Dans changeset, nous avons la ligne validate_required([:first_name, :last_name, :email, :age]), qui donne la liste des champs obligatoires. Ici les 4 champs sont obligatoires.

Nous pouvons travailler avec le changeset dans iex, en créant une variable changeset :

  • changeset= Customer.changeset(%Customer{}, %{first_name: « Eric », last_name: « Durand », email: « eric.durand@gmail.com »})
  • changeset.errors
  • changeset.valid?
  • changeset.changes
C:\CarbonX1\Phoenix\Projets\customer>iex -S mix
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> alias Customer.Customer
Customer.Customer
iex(2)> changeset= Customer.changeset(%Customer{}, %{first_name: "Eric", last_name: "Durand", email: "eric.durand@gmail.
com"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    first_name: "Eric",
    last_name: "Durand",
    email: "eric.durand@gmail.com"
  },
  errors: [age: {"can't be blank", [validation: :required]}],
  data: #Customer.Customer<>,
  valid?: false
>
iex(3)>

L’absence de la propriété age donne une erreur.

Le contrôle des données se fait dans changeset avec validate

La liste validate_required donne les variables obligatoires. Nous pouvons retirer age des variables obligatoires dans :

  • validate_required([:first_name, :last_name, :email, :age]) ‘avant la modification du validate
  • validate_required([:first_name, :last_name, :email]) ‘après la suppresion d’age dans la liste

Mettons à jour le fichier puis relançons la compilation dans iex :

  • clear ‘efface l’ecran
  • recompile() ‘recompile le code

Voyons ce que donne ce changement dans iex :

  • changeset= Customer.changeset(%Customer{}, %{first_name: « Eric », last_name: « Durand », email: « eric.durand@gmail.com »})
  • changeset.errors ‘quand il n’y a pas d’erreur nous avons une liste vide
  • changeset.valid? ‘donne true ou false pour indiquer si le changeset est valide
  • changeset.changes ‘la liste des changements
iex(4)> recompile()
Compiling 1 file (.ex)
:ok
iex(5)> changeset= Customer.changeset(%Customer{}, %{first_name: "Eric", last_name: "Durand", email: "eric.durand@gmail.
com"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    first_name: "Eric",
    last_name: "Durand",
    email: "eric.durand@gmail.com"
  },
  errors: [],
  data: #Customer.Customer<>,
  valid?: true
>
iex(6)> changeset.valid?
true
iex(7)> changeset.changes
%{first_name: "Eric", last_name: "Durand", email: "eric.durand@gmail.com"}
iex(8)> changeset.errors
[]
iex(9)>

On peut ajouter des règles de contrôle complémentaires à notre fonction changeset :

  • validate_length(:first_name, min: 3)
  • validate_length(:first_name, max: 20)
  • validate_format(:email,~r/@/) ‘vérifie que l’email contient le caractère ‘@’ par regex ‘~r’

Lorsqu’on fait des changements dans le code, on peut relancer iex dans la fenêtre terminale avec :

  • clear
  • recompile()

on peut demander au changeset d’afficher les erreurs sur un champ :

  • changeset.errors[:first_name]

La documentation de changeset Ecto donne la liste de toutes les validations possibles.

Modification de la base de données avec Changeset Ecto

Lors de la création de notre schéma de base de données, nous avons créé le Module Customer. Un autre fichier appelé Repo.ex a aussi été créé. Ce module situé en lib/customer/repo.ex est un connecteur vers la base de données PostgreSQL. :

defmodule Customer.Repo do
  use Ecto.Repo,
    otp_app: :customer,
    adapter: Ecto.Adapters.Postgres
end

Insertion des données directement avec Repo

Nous allons utiliser iex pour créer les modifications dans la base de données. Commençons par importer Repo et Customer avec alias :

  • cd C:\CarbonX1\Phoenix\Projets\Customer
  • iex -S mix
  • alias Customer.{Repo, Customer}

Avec Repo, nous pouvons inserer des données dans la base de données directement :

  • Repo.insert(%Customer{email: « customer1@gmail.com »})

Cela montre que nous pouvons inserer des données avec Repo directement dans la base de données sans utiliser changeset. Biensûr, changeset est necessaire pour vérifier que les données sont valides avant de les insérer dans la base de données.

Nous pouvons lire dans la base de données avec Repo :

  • customers = Repo.all(Customer)

Nous avons maintenent dans la variable customers les données en provenance de la base de données.

C:\CarbonX1\Phoenix\Projets\customer>iex -S mix
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> alias Customer.{Repo, Customer}
[Customer.Repo, Customer.Customer]
iex(2)> Repo.insert(%Customer{email: "customer1@gmail.com"})
[debug] QUERY OK source="customers" db=14.3ms decode=7.5ms queue=2.6ms idle=1383.7ms
INSERT INTO "customers" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["customer1@gmail.com", ~N[2023-11-24 09:59:33], ~N[2023-11-24 09:59:33]]
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
{:ok,
 %Customer.Customer{
   __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
   id: 1,
   first_name: nil,
   last_name: nil,
   email: "customer1@gmail.com",
   age: nil,
   inserted_at: ~N[2023-11-24 09:59:33],
   updated_at: ~N[2023-11-24 09:59:33]
 }}
iex(3)> customers = Repo.all(Customer)
[debug] QUERY OK source="customers" db=1.8ms queue=1.0ms idle=1012.3ms
SELECT c0."id", c0."first_name", c0."last_name", c0."email", c0."age", c0."inserted_at", c0."updated_at" FROM "customers" AS c0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
[
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 1,
    first_name: nil,
    last_name: nil,
    email: "customer1@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 09:59:33],
    updated_at: ~N[2023-11-24 09:59:33]
  }
]
iex(4)>

Utilisation de changeset pour contrôler les données

Si nous prenons un changeset avec des données valides :

  • changeset= Customer.changeset(%Customer{}, %{first_name: « Eric », last_name: « Durand », email: « eric.durand@gmail.com »})
  • changeset.valid?
  • Repo.insert(changeset) ‘pour inserer notre changeset dans la base de données
  • customers = Repo.all(Customer)
  • customers
iex(5)> changeset= Customer.changeset(%Customer{}, %{first_name: "Eric", last_name: "Durand", email: "eric.durand@gmail.
com"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    email: "eric.durand@gmail.com",
    first_name: "Eric",
    last_name: "Durand"
  },
  errors: [],
  data: #Customer.Customer<>,
  valid?: true
>
iex(6)> changeset.valid?
true
iex(7)> Repo.insert(changeset)
[debug] QUERY OK source="customers" db=3.9ms queue=1.6ms idle=1550.7ms
INSERT INTO "customers" ("email","first_name","last_name","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["eric.durand@gmail.com", "Eric", "Durand", ~N[2023-11-24 10:03:06], ~N[2023-11-24 10:03:06]]
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
{:ok,
 %Customer.Customer{
   __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
   id: 2,
   first_name: "Eric",
   last_name: "Durand",
   email: "eric.durand@gmail.com",
   age: nil,
   inserted_at: ~N[2023-11-24 10:03:06],
   updated_at: ~N[2023-11-24 10:03:06]
 }}
iex(8)> customers = Repo.all(Customer)
[debug] QUERY OK source="customers" db=4.1ms queue=0.1ms idle=1034.9ms
SELECT c0."id", c0."first_name", c0."last_name", c0."email", c0."age", c0."inserted_at", c0."updated_at" FROM "customers" AS c0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
[
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 1,
    first_name: nil,
    last_name: nil,
    email: "customer1@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 09:59:33],
    updated_at: ~N[2023-11-24 09:59:33]
  },
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 2,
    first_name: "Eric",
    last_name: "Durand",
    email: "eric.durand@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 10:03:06],
    updated_at: ~N[2023-11-24 10:03:06]
  }
]
iex(9)> customers
[
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 1,
    first_name: nil,
    last_name: nil,
    email: "customer1@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 09:59:33],
    updated_at: ~N[2023-11-24 09:59:33]
  },
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 2,
    first_name: "Eric",
    last_name: "Durand",
    email: "eric.durand@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 10:03:06],
    updated_at: ~N[2023-11-24 10:03:06]
  }
]
iex(10)>

Lorsque le changeset n’est pas valide, l’insertion va échouer.

Ajoutons dans nos règles de validation le contrôle sur l’email dans la fonction changeset de Customer :

  • validate_format(:email,~r/@/) ‘(ligne 19) vérifie que l’email contient le caractère ‘@’ par regex ‘~r’
defmodule Customer.Customer do
  use Ecto.Schema
  import Ecto.Changeset

  schema "customers" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :age, :integer

    timestamps()
  end

  @doc false
  def changeset(customer, attrs) do
    customer
    |> cast(attrs, [:first_name, :last_name, :email, :age])
    |> validate_required([:first_name, :last_name, :email])
    |> validate_format(:email,~r/@/)
  end
end

Nous compilons et poursuivons les tests :

  • clear
  • recompile()
  • changeset= Customer.changeset(%Customer{}, %{first_name: « Franck », last_name: « Dupond », email: « emailAvecUneErreurGmail.com »})
  • changeset.valid?
  • changeset.errors
  • Repo.insert(changeset) ‘pour inserer notre changeset dans la base de données

L’insertion est refusée grâce au contrôle de format sur l’email :

iex(13)> recompile()
Compiling 1 file (.ex)
:ok
iex(14)> changeset= Customer.changeset(%Customer{}, %{first_name: "Franck", last_name: "Dupond", email: "emailAvecUneErr
eurGmail.com"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    email: "emailAvecUneErreurGmail.com",
    first_name: "Franck",
    last_name: "Dupond"
  },
  errors: [email: {"has invalid format", [validation: :format]}],
  data: #Customer.Customer<>,
  valid?: false
>
iex(15)> changeset.valid?
false
iex(16)> changeset.errors
[email: {"has invalid format", [validation: :format]}]
iex(17)> Repo.insert(changeset)
{:error,
 #Ecto.Changeset<
   action: :insert,
   changes: %{
     email: "emailAvecUneErreurGmail.com",
     first_name: "Franck",
     last_name: "Dupond"
   },
   errors: [email: {"has invalid format", [validation: :format]}],
   data: #Customer.Customer<>,
   valid?: false
 >}
iex(18)>

Utilisation Query pour accéder aux données

Ecto donne aussi accès à un requêteur avec Query :

  • clear
  • import Ecto.Query
  • Repo.all(from c in Customer, select: c.email) ‘pour récuperer que les emails
  • listEmail = Repo.all(from c in Customer, select: c.email)
  • listEmail

Nous obtenons tous les emails de la base de données.

iex(19)> import Ecto.Query
Ecto.Query
iex(20)> Repo.all(from c in Customer, select: c.email)
[debug] QUERY OK source="customers" db=0.3ms queue=0.4ms idle=1639.2ms
SELECT c0."email" FROM "customers" AS c0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
["customer1@gmail.com", "eric.durand@gmail.com"]
iex(21)> listEmail = Repo.all(from c in Customer, select: c.email)
[debug] QUERY OK source="customers" db=0.4ms idle=1402.9ms
SELECT c0."email" FROM "customers" AS c0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
["customer1@gmail.com", "eric.durand@gmail.com"]
iex(22)> listEmail
["customer1@gmail.com", "eric.durand@gmail.com"]
iex(23)>

Pour obtenir un seul record, nous avons :

  • eric = Repo.one(from c in Customer, where: c.email== « eric.durand@gmail.com ») ‘pour récuperer eric par son email
  • eric ‘la variable contient le record « Eric Durand »
iex(23)> eric = Repo.one(from c in Customer, where:  c.email== "eric.durand@gmail.com")
[debug] QUERY OK source="customers" db=3.9ms queue=3.5ms idle=1241.4ms
SELECT c0."id", c0."first_name", c0."last_name", c0."email", c0."age", c0."inserted_at", c0."updated_at" FROM "customers" AS c0 WHERE (c0."email" = 'eric.durand@gmail.com') []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
%Customer.Customer{
  __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
  id: 2,
  first_name: "Eric",
  last_name: "Durand",
  email: "eric.durand@gmail.com",
  age: nil,
  inserted_at: ~N[2023-11-24 10:03:06],
  updated_at: ~N[2023-11-24 10:03:06]
}
iex(24)> eric
%Customer.Customer{
  __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
  id: 2,
  first_name: "Eric",
  last_name: "Durand",
  email: "eric.durand@gmail.com",
  age: nil,
  inserted_at: ~N[2023-11-24 10:03:06],
  updated_at: ~N[2023-11-24 10:03:06]
}
iex(25)>

La modification du record Eric Durand se fait avec la création d’un changeset de modification :

  • changeset = Customer.changeset(%Customer{}, params) ‘version précédente

Pour modiufier l’adresse email d’Eric Durand :

  • clear
  • changeset = Customer.changeset(eric, %{email: « e.durand@gmail »})
  • changeset.valid? ‘vérifier si notre changeset est valide
  • changeset.data ‘voir nos données avant changement
  • Repo.update(changeset) ‘modifier la base de données avec notre changeset
  • Repo.all(Customer) ‘pour voir les changements dans la base de données
iex(26)> changeset = Customer.changeset(eric, %{email: "e.durand@gmail"})
#Ecto.Changeset<
  action: nil,
  changes: %{email: "e.durand@gmail"},
  errors: [],
  data: #Customer.Customer<>,
  valid?: true
>
iex(27)> changeset.valid?
true
iex(28)> changeset.data
%Customer.Customer{
  __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
  id: 2,
  first_name: "Eric",
  last_name: "Durand",
  email: "eric.durand@gmail.com",
  age: nil,
  inserted_at: ~N[2023-11-24 10:03:06],
  updated_at: ~N[2023-11-24 10:03:06]
}
iex(29)> Repo.update(changeset)
[debug] QUERY OK source="customers" db=8.9ms queue=4.8ms idle=1233.1ms
UPDATE "customers" SET "email" = $1, "updated_at" = $2 WHERE "id" = $3 ["e.durand@gmail", ~N[2023-11-24 10:21:01], 2]
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
{:ok,
 %Customer.Customer{
   __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
   id: 2,
   first_name: "Eric",
   last_name: "Durand",
   email: "e.durand@gmail",
   age: nil,
   inserted_at: ~N[2023-11-24 10:03:06],
   updated_at: ~N[2023-11-24 10:21:01]
 }}
iex(30)> Repo.all(Customer)
[debug] QUERY OK source="customers" db=1.2ms queue=0.1ms idle=1905.4ms
SELECT c0."id", c0."first_name", c0."last_name", c0."email", c0."age", c0."inserted_at", c0."updated_at" FROM "customers" AS c0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:376
[
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 1,
    first_name: nil,
    last_name: nil,
    email: "customer1@gmail.com",
    age: nil,
    inserted_at: ~N[2023-11-24 09:59:33],
    updated_at: ~N[2023-11-24 09:59:33]
  },
  %Customer.Customer{
    __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
    id: 2,
    first_name: "Eric",
    last_name: "Durand",
    email: "e.durand@gmail",
    age: nil,
    inserted_at: ~N[2023-11-24 10:03:06],
    updated_at: ~N[2023-11-24 10:21:01]
  }
]
iex(31)>

Conclusion

Nous avons fait un petit tour sur l’utilisation d’Ecto avec :

  • une présentation du changeset
  • l’utilisation de Repo
Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire