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.

ソース情報

リポジトリ
TheBushidoCollective/han
ソースの最終更新活動
2026年2月11日 17:47
検出された SKILL.md の言語
英語
スター
198
フォーク
22

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る