Skip to main content

ecto-changesets

Use when validating and casting data with Ecto changesets including field validation, constraints, nested changesets, and data transformation. Use for ensuring data integrity before database operations.

Quellinformationen

Repository
TheBushidoCollective/han
Letzte Quellaktivität
11. Februar 2026 um 17:47
Erkannte Sprache von SKILL.md
Englisch
Sterne
198
Forks
22

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
ecto-changesets
user-invocable
false
description
Use when validating and casting data with Ecto changesets including field validation, constraints, nested changesets, and data transformation. Use for ensuring data integrity before database operations.
allowed-tools
["Bash","Read"]
# Ecto Changesets Master Ecto changesets to validate, cast, and transform data before database operations. This skill covers changeset creation, validation, constraints, handling associations, and advanced patterns for maintaining data integrity. ## Basic Changeset ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :name, :string field :email, :string field :age, :integer timestamps() end def changeset(user, params \\ %{}) do user |> cast(params, [:name, :email, :age]) |> validate_required([:name, :email]) end end # Usage changeset = MyApp.User.changeset(%MyApp.User{}, %{name: "John", email: "john@example.com"}) ``` Changesets filter and validate parameters before they're applied to a struct. The `cast/3` function specifies which fields can be changed, and `validate_required/2` ensures specific fields are present. ## Creating and Validating Changesets ```elixir defmodule MyApp.Person do use Ecto.Schema import Ecto.Changeset schema "people" do field :first_name, :string field :last_name, :string field :age, :integer timestamps() end def changeset(person, params \\ %{}) do person |> cast(params, [:first_name, :last_name, :age]) |> validate_required([:first_name, :last_name]) |> validate_number(:age, greater_than_or_equal_to: 0) end end # Create changeset changeset = MyApp.Person.changeset(%MyApp.Person{}, %{first_name: "Jane"}) # Check validity changeset.valid? # false, last_name is missing # Access errors changeset.errors # [first_name: {"can't be blank", [validation: :required]}, # last_name: {"can't be blank", [validation: :required]}] ``` The `valid?` field indicates whether the changeset has any errors. The `errors` field contains a keyword list of validation failures with error messages and metadata. ## Inserting with Changesets ```elixir person = %MyApp.Person{} changeset = MyApp.Person.changeset(person, %{ first_name: "John", last_name: "Doe", age: 30 }) case MyApp.Repo.insert(changeset) do {:ok, person} -> # Successfully inserted IO.puts("Created person with ID: #{person.id}") {:error, changeset} -> # Validation or constraint errors IO.inspect(changeset.errors) end ``` The `Repo.insert/1` function accepts a changeset and returns `{:ok, struct}` on success or `{:error, changeset}` on failure. Pattern matching makes error handling straightforward. ## Updating with Changesets ```elixir person = MyApp.Repo.get!(MyApp.Person, 1) changeset = MyApp.Person.changeset(person, %{age: 31}) case MyApp.Repo.update(changeset) do {:ok, updated_person} -> # Successfully updated IO.puts("Updated person age to: #{updated_person.age}") {:error, changeset} -> # Validation or constraint errors IO.inspect(changeset.errors) end ``` Updates work similarly to inserts, but start with an existing struct from the database. The changeset tracks which fields have changed. ## Type Casting ```elixir changeset = Ecto.Changeset.cast(%MyApp.User{}, %{"age" => "25"}, [:age]) user = MyApp.Repo.insert!(changeset) user.age # 25 (integer, not string) ``` The `cast/3` function automatically converts parameter values to their schema-defined types. Strings like "25" are converted to integers when the field type is `:integer`. ## Field Validations ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :username, :string field :age, :integer field :bio, :string field :website, :string timestamps() end def changeset(user, params \\ %{}) do user |> cast(params, [:email, :username, :age, :bio, :website]) |> validate_required([:email, :username]) |> validate_format(:email, ~r/@/) |> validate_length(:username, min: 3, max: 20) |> validate_length(:bio, max: 500) |> validate_number(:age, greater_than: 0, less_than: 150) |> validate_inclusion(:age, 18..100) |> validate_format(:website, ~r/^https?:\/\//) end end ``` Ecto provides many built-in validators including `validate_format/3` for regex patterns, `validate_length/3` for string lengths, `validate_number/3` for numeric constraints, and `validate_inclusion/3` for allowed values. ## Custom Validation Functions ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :password, :string, virtual: true field :password_hash, :string timestamps() end def changeset(user, params \\ %{}) do user |> cast(params, [:email, :password]) |> validate_required([:email, :password]) |> validate_email_format() |> validate_password_strength() |> hash_password() end defp validate_email_format(changeset) do changeset |> validate_format(:email, ~r/@/, message: "must be a valid email") |> validate_length(:email, max: 255) end defp validate_password_strength(changeset) do validate_change(changeset, :password, fn :password, password -> cond do String.length(password) < 8 -> [password: "must be at least 8 characters"] not String.match?(password, ~r/[A-Z]/) -> [password: "must contain at least one uppercase letter"] not String.match?(password, ~r/[0-9]/) -> [password: "must contain at least one number"] true -> [] end end) end defp hash_password(changeset) do case changeset do %Ecto.Changeset{valid?: true, changes: %{password: password}} -> put_change(changeset, :password_hash, hash_password_value(password)) _ -> changeset end end defp hash_password_value(password) do # Use a real hashing library like Argon2 or Bcrypt :crypto.hash(:sha256, password) |> Base.encode64() end end ``` Custom validation functions use `validate_change/3` to add custom logic. The `put_change/3` function modifies changeset values, useful for transformations like password hashing. ## Constraint Validations ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :username, :string timestamps() end def changeset(user, params \\ %{}) do user |> cast(params, [:email, :username]) |> validate_required([:email, :username]) |> unique_constraint(:email) |> unique_constraint(:username) end end # Usage case MyApp.Repo.insert(changeset) do {:ok, user} -> # Success {:error, changeset} -> # Will contain unique constraint error if email/username exists changeset.errors end ``` Constraint validations check database-level constraints like unique indexes. They only run when the changeset is inserted or updated, not during validation. ## Unique Constraint with Custom Error Message ```elixir def changeset(user, params \\ %{}) do user |> cast(params, [:email]) |> validate_required([:email]) |> unique_constraint(:email, name: :users_email_index, message: "has already been taken") end ``` The `unique_constraint/3` function accepts options to specify the constraint name and customize the error message. This maps database constraint violations to user-friendly errors. ## Foreign Key Constraints ```elixir defmodule MyApp.Comment do use Ecto.Schema import Ecto.Changeset schema "comments" do field :body, :string belongs_to :post, MyApp.Post timestamps() end def changeset(comment, params \\ %{}) do comment |> cast(params, [:body, :post_id]) |> validate_required([:body, :post_id]) |> foreign_key_constraint(:post_id) end end ``` The `foreign_key_constraint/3` function validates that foreign key relationships are valid. If you try to create a comment with a non-existent post_id, the constraint will catch it. ## Check Constraints ```elixir def changeset(product, params \\ %{}) do product |> cast(params, [:price, :discount_price]) |> validate_required([:price]) |> check_constraint(:discount_price, name: :discount_price_must_be_less_than_price, message: "must be less than the regular price") end ``` Check constraints validate arbitrary database-level rules. The constraint must be defined in your migration with the same name. ## Changeset Composition ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :password, :string, virtual: true field :password_hash, :string field :role, :string timestamps() end def registration_changeset(user, params \\ %{}) do user |> cast(params, [:email, :password]) |> validate_required([:email, :password]) |> validate_email() |> validate_password() |> hash_password() |> put_change(:role, "user") end def admin_changeset(user, params \\ %{}) do user |> cast(params, [:email, :password, :role]) |> validate_required([:email, :role]) |> validate_email() |> validate_inclusion(:role, ["user", "admin", "moderator"]) |> maybe_hash_password() end defp validate_email(changeset) do changeset |> validate_format(:email, ~r/@/) |> unique_constraint(:email) end defp validate_password(changeset) do validate_length(changeset, :password, min: 8) end defp hash_password(changeset) do case get_change(changeset, :password) do nil -> changeset password -> put_change(changeset, :password_hash, hash(password)) end end defp maybe_hash_password(changeset) do case get_change(changeset, :password) do nil -> changeset password -> hash_password(changeset) end end defp hash(password), do: :crypto.hash(:sha256, password) end ``` Different changesets can be used for different contexts (registration vs. admin updates). This keeps validation logic focused and prevents unintended changes. ## Embedded Changeset Validation ```elixir defmodule MyApp.UserProfile do use Ecto.Schema import Ecto.Changeset embedded_schema do field :online, :boolean field :dark_mode, :boolean field :visibility, Ecto.Enum, values: [:public, :private, :friends_only] end def changeset(profile, attrs \\ %{}) do profile |> cast(attrs, [:online, :dark_mode, :visibility]) |> validate_required([:online, :visibility]) end end defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :full_name, :string field :email, :string embeds_one :profile, MyApp.UserProfile timestamps() end def changeset(user, attrs \\ %{}) do user |> cast(attrs, [:full_name, :email]) |> cast_embed(:profile, required: true) end end # Usage changeset = MyApp.User.changeset(%MyApp.User{}, %{ full_name: "John Doe", email: "john@example.com", profile: %{online: true, visibility: :public} }) changeset.valid? # true ``` The `cast_embed/3` function validates embedded schemas using their own changeset functions. Validation errors in embedded data propagate to the parent changeset. ## Custom Embedded Changeset Function ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :full_name, :string field :email, :string embeds_one :profile, Profile do field :online, :boolean field :dark_mode, :boolean field :visibility, Ecto.Enum, values: [:public, :private, :friends_only] end timestamps() end def changeset(user, attrs \\ %{}) do user |> cast(attrs, [:full_name, :email]) |> cast_embed(:profile, required: true, with: &profile_changeset/2) end
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen