Repo, Changesets et requêtes
Utilisez `Ecto.Repo` pour les interactions avec la base de données, `Changesets` pour valider les données et construisez des requêtes avec `Ecto.Query`.
Repo, Changesets et requêtes est une leçon Elixir & Phoenix: Scalable Backend Development gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Elixir & Phoenix: Scalable Backend Development, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Elixir & Phoenix: Scalable Backend Development comprend 4 leçons au total.
Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.
Ecto.Repo: Database Gateway
In Elixir, Ecto.Repo is your primary interface for interacting with your database. Think of it as the central hub that handles all your database operations.
It provides functions for inserting, updating, deleting, and querying records, abstracting away the specifics of the underlying database technology.
- Connects your Elixir application to the database.
- Manages database transactions.
- Executes Ecto queries.
Saving Data with Repo.insert
To add new data to your database, you'll use Ecto.Repo.insert/2. This function takes a changeset (which we'll cover next) and attempts to save it as a new record.
If successful, it returns {:ok, record}; otherwise, it returns {:error, changeset} with validation errors.
defmodule MyApp.Product do
use Ecto.Schema
import Ecto.Changeset
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
timestamps()
end
def changeset(product, attrs) do
product
|> cast(attrs, [:name, :price, :stock])
|> validate_required([:name, :price])
|> validate_number(:price, greater_than: 0)
|> validate_number(:stock, greater_than_or_equal_to: 0)
end
end
defmodule MyApp.SimulatedRepo do
alias MyApp.Product
def insert(changeset) do
if Ecto.Changeset.valid?(changeset) do
data = Ecto.Changeset.apply_action(changeset, :insert)
# Simulate successful insert with a mock ID and timestamps
{:ok, %Product{data | id: 1, inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}}
else
{:error, changeset}
end
end
end
defmodule InsertExample do
alias MyApp.{Product, SimulatedRepo}
def run() do
# Prepare product attributes
attrs = %{name: "Elixir Book", price: Decimal.new("39.99"), stock: 100}
# Create a changeset
changeset = Product.changeset(%Product{}, attrs)
# Simulate inserting the product
case SimulatedRepo.insert(changeset) do
{:ok, product} ->
IO.puts "Successfully inserted product:"
IO.inspect product
{:error, changeset} ->
IO.puts "Failed to insert product:"
IO.inspect Ecto.Changeset.errors(changeset)
end
end
end
# To run this, execute: elixir -e "InsertExample.run()"Retrieving Records with Repo
You can fetch records from the database using Ecto.Repo.get/3 to retrieve a single record by its primary key, or Ecto.Repo.all/2 to get multiple records.
get(Schema, id): Fetches one record by ID. Returns the struct ornil.all(Query): Fetches all records matching a given query.
defmodule MyApp.Product do
use Ecto.Schema
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
timestamps()
end
end
defmodule MyApp.SimulatedRepo do
alias MyApp.Product
def get(id, _query \\ nil) do
IO.puts "Simulating fetching product with ID: #{id}"
case id do
1 -> %Product{id: 1, name: "Laptop", price: Decimal.new("1200.00"), stock: 5,
inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}
_ -> nil
end
end
def all(_query \\ nil) do
IO.puts "Simulating fetching all products..."
[
%Product{id: 1, name: "Laptop", price: Decimal.new("1200.00"), stock: 5,
inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()},
%Product{id: 2, name: "Mouse", price: Decimal.new("25.50"), stock: 20,
inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}
]
end
end
defmodule FetchExample do
alias MyApp.SimulatedRepo
def run() do
# Fetch a single product by ID
IO.puts "\n--- Fetching by ID ---"
product = SimulatedRepo.get(1)
IO.inspect product
# Fetch all products
IO.puts "\n--- Fetching all ---"
products = SimulatedRepo.all()
IO.inspect products
end
end
# To run this, execute: elixir -e "FetchExample.run()"Data Safety with Changesets
Ecto.Changeset is a powerful mechanism for validating, casting, and manipulating data before it interacts with your database. It ensures data integrity and security.
Instead of directly passing raw user input to the database, you first pass it through a changeset. The changeset tracks all proposed changes and any associated errors.
- Casting: Converts input data into the correct Elixir types.
- Validation: Checks if the data meets predefined rules (e.g., required fields, data formats).
- Authorization: Can even be used for field-level permission checks.
Casting and Basic Validation
The first step with a changeset is usually Ecto.Changeset.cast/4. This function takes a struct (or an empty struct), parameters (like user input), and a list of allowed fields. It converts the input into the correct types.
After casting, you can apply validations like validate_required/3 to ensure essential fields are present.
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age])
|> validate_required([:name, :email])
end
end
defmodule ChangesetCastValidateExample do
alias MyApp.User
def run() do
IO.puts "--- Valid Input ---"
valid_params = %{"name" => "Alice", "email" => "alice@example.com", "age" => "30"}
valid_changeset = User.changeset(%User{}, valid_params)
IO.puts "Is valid? #{Ecto.Changeset.valid?(valid_changeset)}"
IO.inspect Ecto.Changeset.changes(valid_changeset)
IO.puts "\n--- Invalid Input (missing email) ---"
invalid_params = %{"name" => "Bob", "age" => "25"}
invalid_changeset = User.changeset(%User{}, invalid_params)
IO.puts "Is valid? #{Ecto.Changeset.valid?(invalid_changeset)}"
IO.inspect Ecto.Changeset.errors(invalid_changeset)
end
end
# To run this, execute: elixir -e "ChangesetCastValidateExample.run()"Deeper Changeset Validations
Beyond basic requirements, Ecto offers many validation helpers to enforce specific rules:
validate_length/3: Checks string length.validate_number/3: Validates numeric ranges.validate_format/3: Uses regular expressions for complex patterns (e.g., email format).
These help you build robust data models.
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age])
|> validate_required([:name, :email])
|> validate_length(:name, min: 3, max: 50)
|> validate_number(:age, greater_than_or_equal_to: 18)
|> validate_format(:email, ~r/@/, message: "must contain an @ symbol")
end
end
defmodule ChangesetDeepValidateExample do
alias MyApp.User
def run() do
IO.puts "--- Valid User ---"
user_attrs_1 = %{"name" => "Charlie", "email" => "charlie@example.com", "age" => "22"}
changeset_1 = User.changeset(%User{}, user_attrs_1)
IO.puts "Valid? #{Ecto.Changeset.valid?(changeset_1)}"
IO.inspect Ecto.Changeset.errors(changeset_1)
IO.puts "\n--- Invalid User (short name, young age, bad email) ---"
user_attrs_2 = %{"name" => "Al", "email" => "al.example.com", "age" => "17"}
changeset_2 = User.changeset(%User{}, user_attrs_2)
IO.puts "Valid? #{Ecto.Changeset.valid?(changeset_2)}"
IO.inspect Ecto.Changeset.errors(changeset_2)
end
end
# To run this, execute: elixir -e "ChangesetDeepValidateExample.run()"Applying Changeset Actions
Once a changeset is validated, you can use it to perform database actions like inserting or updating records. The Ecto.Changeset.valid?/1 function tells you if the changeset is ready.
If invalid, Ecto.Changeset.errors/1 will provide a detailed list of what went wrong, which is crucial for providing user feedback.
defmodule MyApp.Product do
use Ecto.Schema
import Ecto.Changeset
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
timestamps()
end
def changeset(product, attrs) do
product
|> cast(attrs, [:name, :price, :stock])
|> validate_required([:name, :price])
|> validate_number(:price, greater_than: 0)
end
end
defmodule ChangesetApplyExample do
alias MyApp.Product
def run() do
IO.puts "--- Valid Product Changeset ---"
valid_attrs = %{"name" => "Widget", "price" => "9.99", "stock" => "50"}
valid_changeset = Product.changeset(%Product{}, valid_attrs)
if Ecto.Changeset.valid?(valid_changeset) do
IO.puts "Changeset is valid. Proposed changes:"
IO.inspect Ecto.Changeset.changes(valid_changeset)
# In a real app, you'd call Repo.insert(valid_changeset) here
else
IO.puts "Changeset is invalid. Errors:"
IO.inspect Ecto.Changeset.errors(valid_changeset)
end
IO.puts "\n--- Invalid Product Changeset (missing price) ---"
invalid_attrs = %{"name" => "Gadget", "stock" => "20"}
invalid_changeset = Product.changeset(%Product{}, invalid_attrs)
if Ecto.Changeset.valid?(invalid_changeset) do
IO.puts "Changeset is valid. Proposed changes:"
IO.inspect Ecto.Changeset.changes(invalid_changeset)
else
IO.puts "Changeset is invalid. Errors:"
IO.inspect Ecto.Changeset.errors(invalid_changeset)
end
end
end
# To run this, execute: elixir -e "ChangesetApplyExample.run()"Building Queries with Ecto.Query
While Repo.get/3 and Repo.all/2 are useful for simple fetches, Ecto.Query provides a powerful, composable language for building complex database queries.
You start a query with the from macro, specifying the schema you want to query and giving it an alias.
Ecto queries are Elixir expressions that are translated into SQL by Ecto.
defmodule MyApp.Product do
use Ecto.Schema
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
end
end
defmodule EctoQueryFromExample do
import Ecto.Query
alias MyApp.Product
def run() do
# A simple query to select all products
query = from p in Product
IO.puts "Basic query structure:"
IO.inspect query
IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
end
end
# To run this, execute: elixir -e "EctoQueryFromExample.run()"Querying: Filtering & Selecting
You can refine your queries using clauses like where to filter records based on conditions, and select to specify which fields you want to retrieve.
where: p.stock > 0: Filters records where stock is greater than 0.select: p.name: Retrieves only the 'name' field.
defmodule MyApp.Product do
use Ecto.Schema
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
end
end
defmodule EctoQueryWhereSelectExample do
import Ecto.Query
alias MyApp.Product
def run() do
# Query for products with stock > 0, selecting only their names
query = from p in Product,
where: p.stock > 0,
select: p.name
IO.puts "Query structure with where and select:"
IO.inspect query
IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
end
end
# To run this, execute: elixir -e "EctoQueryWhereSelectExample.run()"Querying: Ordering & Limiting
To control the presentation of your results, you can use order_by to sort records and limit to restrict the number of records returned.
order_by: [desc: p.price]: Sorts by price in descending order.limit: 5: Returns only the first 5 matching records.
These are crucial for pagination and displaying sorted lists.
defmodule MyApp.Product do
use Ecto.Schema
schema "products" do
field :name, :string
field :price, :decimal
field :stock, :integer, default: 0
end
end
defmodule EctoQueryOrderLimitExample do
import Ecto.Query
alias MyApp.Product
def run() do
# Query for products, ordered by price descending, limited to 3
query = from p in Product,
order_by: [desc: p.price],
limit: 3
IO.puts "Query structure with order_by and limit:"
IO.inspect query
IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
end
end
# To run this, execute: elixir -e "EctoQueryOrderLimitExample.run()"Quick Check: Ecto Core
Which of the following statements about Ecto.Changeset is TRUE?
Recap: Repo, Changesets & Queries
Great job! In this lesson, you've learned the core components for data persistence in Elixir with Ecto:
- Ecto.Repo: The gateway for all database interactions.
- Ecto.Changeset: Essential for casting, validating, and tracking changes to your data, ensuring integrity.
- Ecto.Query: A powerful and flexible way to construct complex database queries using Elixir syntax.
Mastering these three pillars is fundamental to building robust and reliable data-driven applications in Elixir!
Apprends Elixir avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 12
- Leçons
- 48
Questions Fréquemment Posées
La leçon « Repo, Changesets et requêtes » est-elle gratuite ?
Oui — le texte complet de « Repo, Changesets et requêtes » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Elixir & Phoenix: Scalable Backend Development, passe à CoddyKit PRO. Le cours Elixir & Phoenix: Scalable Backend Development comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Repo, Changesets et requêtes » ?
Utilisez `Ecto.Repo` pour les interactions avec la base de données, `Changesets` pour valider les données et construisez des requêtes avec `Ecto.Query`. Tu pratiques Elixir & Phoenix: Scalable Backend Development avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Elixir & Phoenix: Scalable Backend Development ?
Aucune expérience préalable n'est requise. Elixir & Phoenix: Scalable Backend Development sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « Repo, Changesets et requêtes » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Elixir & Phoenix: Scalable Backend Development ?
Oui. Chaque leçon Elixir & Phoenix: Scalable Backend Development inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Schémas Ecto et migrations de base de données
- Repo, Changesets et requêtes
- Associations et schémas intégrés
- Transactions et Ecto.Multi