elixir-ecto-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseElixir Ecto Patterns
Elixir Ecto 模式
Master Ecto, Elixir's database wrapper and query generator. This skill
covers schemas, changesets, queries, associations, and transactions for
building robust database applications.
精通Ecto——Elixir的数据库包装器和查询生成器。本技能涵盖Schema、Changeset、查询、关联和事务,助力构建健壮的数据库应用。
Schema Definition
Schema 定义
elixir
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
field :is_active, :boolean, default: true
field :role, Ecto.Enum, values: [:user, :admin, :moderator]
has_many :posts, MyApp.Post
belongs_to :organization, MyApp.Organization
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age, :is_active, :role])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0, less_than: 150)
|> unique_constraint(:email)
end
endelixir
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
field :is_active, :boolean, default: true
field :role, Ecto.Enum, values: [:user, :admin, :moderator]
has_many :posts, MyApp.Post
belongs_to :organization, MyApp.Organization
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age, :is_active, :role])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0, less_than: 150)
|> unique_constraint(:email)
end
endChangeset Validations
Changeset 验证
elixir
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :text
field :published, :boolean, default: false
field :tags, {:array, :string}, default: []
belongs_to :user, MyApp.User
timestamps()
end
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published, :tags, :user_id])
|> validate_required([:title, :body, :user_id])
|> validate_length(:title, min: 3, max: 100)
|> validate_length(:body, min: 10)
|> foreign_key_constraint(:user_id)
end
def publish_changeset(post) do
post
|> change(published: true)
end
endelixir
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :text
field :published, :boolean, default: false
field :tags, {:array, :string}, default: []
belongs_to :user, MyApp.User
timestamps()
end
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published, :tags, :user_id])
|> validate_required([:title, :body, :user_id])
|> validate_length(:title, min: 3, max: 100)
|> validate_length(:body, min: 10)
|> foreign_key_constraint(:user_id)
end
def publish_changeset(post) do
post
|> change(published: true)
end
endBasic Queries
基础查询
elixir
import Ecto.Queryelixir
import Ecto.QueryGet all users
Get all users
Repo.all(User)
Repo.all(User)
Get user by ID
Get user by ID
Repo.get(User, 1)
Repo.get!(User, 1) # Raises if not found
Repo.get(User, 1)
Repo.get!(User, 1) # Raises if not found
Get by specific field
Get by specific field
Repo.get_by(User, email: "user@example.com")
Repo.get_by(User, email: "user@example.com")
Filter with where clause
Filter with where clause
query = from u in User, where: u.age > 18
Repo.all(query)
query = from u in User, where: u.age > 18
Repo.all(query)
Select specific fields
Select specific fields
query = from u in User, select: {u.id, u.name}
Repo.all(query)
query = from u in User, select: {u.id, u.name}
Repo.all(query)
Order results
Order results
query = from u in User, order_by: [desc: u.inserted_at]
Repo.all(query)
query = from u in User, order_by: [desc: u.inserted_at]
Repo.all(query)
Limit and offset
Limit and offset
query = from u in User, limit: 10, offset: 20
Repo.all(query)
undefinedquery = from u in User, limit: 10, offset: 20
Repo.all(query)
undefinedComplex Queries
复杂查询
elixir
undefinedelixir
undefinedCombining multiple conditions
Combining multiple conditions
query =
from u in User,
where: u.is_active == true,
where: u.age >= 18,
order_by: [desc: u.inserted_at],
limit: 10
Repo.all(query)
query =
from u in User,
where: u.is_active == true,
where: u.age >= 18,
order_by: [desc: u.inserted_at],
limit: 10
Repo.all(query)
Using pipe syntax
Using pipe syntax
User
|> where([u], u.is_active == true)
|> where([u], u.age >= 18)
|> order_by([u], desc: u.inserted_at)
|> limit(10)
|> Repo.all()
User
|> where([u], u.is_active == true)
|> where([u], u.age >= 18)
|> order_by([u], desc: u.inserted_at)
|> limit(10)
|> Repo.all()
Dynamic queries
Dynamic queries
def filter_users(params) do
User
|> filter_by_name(params["name"])
|> filter_by_age(params["min_age"])
|> Repo.all()
end
defp filter_by_name(query, nil), do: query
defp filter_by_name(query, name) do
where(query, [u], ilike(u.name, ^"%#{name}%"))
end
defp filter_by_age(query, nil), do: query
defp filter_by_age(query, min_age) do
where(query, [u], u.age >= ^min_age)
end
undefineddef filter_users(params) do
User
|> filter_by_name(params["name"])
|> filter_by_age(params["min_age"])
|> Repo.all()
end
defp filter_by_name(query, nil), do: query
defp filter_by_name(query, name) do
where(query, [u], ilike(u.name, ^"%#{name}%"))
end
defp filter_by_age(query, nil), do: query
defp filter_by_age(query, min_age) do
where(query, [u], u.age >= ^min_age)
end
undefinedAssociations and Preloading
关联与预加载
elixir
undefinedelixir
undefinedPreload associations
Preload associations
user = Repo.get(User, 1) |> Repo.preload(:posts)
user = Repo.get(User, 1) |> Repo.preload(:posts)
Preload nested associations
Preload nested associations
user = Repo.get(User, 1) |> Repo.preload([posts: :comments])
user = Repo.get(User, 1) |> Repo.preload([posts: :comments])
Query with preload
Query with preload
query = from u in User, preload: [:posts, :organization]
Repo.all(query)
query = from u in User, preload: [:posts, :organization]
Repo.all(query)
Custom preload query
Custom preload query
posts_query = from p in Post, where: p.published == true
query = from u in User, preload: [posts: ^posts_query]
Repo.all(query)
posts_query = from p in Post, where: p.published == true
query = from u in User, preload: [posts: ^posts_query]
Repo.all(query)
Join and preload
Join and preload
query =
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.all(query)
undefinedquery =
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.all(query)
undefinedAggregations and Grouping
聚合与分组
elixir
undefinedelixir
undefinedCount records
Count records
Repo.aggregate(User, :count)
Repo.aggregate(User, :count)
Count with condition
Count with condition
query = from u in User, where: u.is_active == true
Repo.aggregate(query, :count)
query = from u in User, where: u.is_active == true
Repo.aggregate(query, :count)
Other aggregations
Other aggregations
Repo.aggregate(User, :avg, :age)
Repo.aggregate(User, :sum, :age)
Repo.aggregate(User, :max, :age)
Repo.aggregate(User, :avg, :age)
Repo.aggregate(User, :sum, :age)
Repo.aggregate(User, :max, :age)
Group by
Group by
query =
from u in User,
group_by: u.role,
select: {u.role, count(u.id)}
Repo.all(query)
query =
from u in User,
group_by: u.role,
select: {u.role, count(u.id)}
Repo.all(query)
Group with having
Group with having
query =
from u in User,
group_by: u.role,
having: count(u.id) > 5,
select: {u.role, count(u.id)}
Repo.all(query)
undefinedquery =
from u in User,
group_by: u.role,
having: count(u.id) > 5,
select: {u.role, count(u.id)}
Repo.all(query)
undefinedInserting and Updating
插入与更新
elixir
undefinedelixir
undefinedInsert with changeset
Insert with changeset
attrs = %{name: "John", email: "john@example.com", age: 30}
%User{}
|> User.changeset(attrs)
|> Repo.insert()
attrs = %{name: "John", email: "john@example.com", age: 30}
%User{}
|> User.changeset(attrs)
|> Repo.insert()
Insert without changeset
Insert without changeset
Repo.insert(%User{name: "Jane", email: "jane@example.com"})
Repo.insert(%User{name: "Jane", email: "jane@example.com"})
Update
Update
user = Repo.get(User, 1)
user
|> User.changeset(%{age: 31})
|> Repo.update()
user = Repo.get(User, 1)
user
|> User.changeset(%{age: 31})
|> Repo.update()
Update all
Update all
query = from u in User, where: u.is_active == false
Repo.update_all(query, set: [is_active: true])
query = from u in User, where: u.is_active == false
Repo.update_all(query, set: [is_active: true])
Delete
Delete
user = Repo.get(User, 1)
Repo.delete(user)
user = Repo.get(User, 1)
Repo.delete(user)
Delete all
Delete all
query = from u in User, where: u.is_active == false
Repo.delete_all(query)
undefinedquery = from u in User, where: u.is_active == false
Repo.delete_all(query)
undefinedTransactions
事务
elixir
undefinedelixir
undefinedBasic transaction
Basic transaction
Repo.transaction(fn ->
user = Repo.insert!(%User{name: "Alice"})
Repo.insert!(%Post{title: "First post", user_id: user.id})
end)
Repo.transaction(fn ->
user = Repo.insert!(%User{name: "Alice"})
Repo.insert!(%Post{title: "First post", user_id: user.id})
end)
Multi for complex transactions
Multi for complex transactions
alias Ecto.Multi
Multi.new()
|> Multi.insert(:user, User.changeset(%User{}, user_attrs))
|> Multi.insert(:post, fn %{user: user} ->
Post.changeset(%Post{}, Map.put(post_attrs, :user_id, user.id))
end)
|> Multi.run(:send_email, fn _repo, %{user: user} ->
send_welcome_email(user)
end)
|> Repo.transaction()
undefinedalias Ecto.Multi
Multi.new()
|> Multi.insert(:user, User.changeset(%User{}, user_attrs))
|> Multi.insert(:post, fn %{user: user} ->
Post.changeset(%Post{}, Map.put(post_attrs, :user_id, user.id))
end)
|> Multi.run(:send_email, fn _repo, %{user: user} ->
send_welcome_email(user)
end)
|> Repo.transaction()
undefinedEmbedded Schemas
嵌入Schema
elixir
defmodule MyApp.Address do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :street, :string
field :city, :string
field :state, :string
field :zip, :string
end
def changeset(address, attrs) do
address
|> cast(attrs, [:street, :city, :state, :zip])
|> validate_required([:city, :state])
end
end
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
embeds_one :address, MyApp.Address
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name])
|> cast_embed(:address, required: true)
end
endelixir
defmodule MyApp.Address do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :street, :string
field :city, :string
field :state, :string
field :zip, :string
end
def changeset(address, attrs) do
address
|> cast(attrs, [:street, :city, :state, :zip])
|> validate_required([:city, :state])
end
end
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
embeds_one :address, MyApp.Address
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name])
|> cast_embed(:address, required: true)
end
endCustom Ecto Types
自定义Ecto类型
elixir
defmodule MyApp.Encrypted do
use Ecto.Type
def type, do: :binary
def cast(value) when is_binary(value), do: {:ok, value}
def cast(_), do: :error
def dump(value) when is_binary(value) do
{:ok, encrypt(value)}
end
def load(value) when is_binary(value) do
{:ok, decrypt(value)}
end
defp encrypt(value) do
# Encryption logic
value
end
defp decrypt(value) do
# Decryption logic
value
end
endelixir
defmodule MyApp.Encrypted do
use Ecto.Type
def type, do: :binary
def cast(value) when is_binary(value), do: {:ok, value}
def cast(_), do: :error
def dump(value) when is_binary(value) do
{:ok, encrypt(value)}
end
def load(value) when is_binary(value) do
{:ok, decrypt(value)}
end
defp encrypt(value) do
# Encryption logic
value
end
defp decrypt(value) do
# Decryption logic
value
end
endUsage in schema
Usage in schema
schema "users" do
field :secret, MyApp.Encrypted
end
undefinedschema "users" do
field :secret, MyApp.Encrypted
end
undefinedWhen to Use This Skill
何时使用本技能
Use elixir-ecto-patterns when you need to:
- Build database-backed Elixir applications
- Define schemas and data models with validations
- Write complex database queries with Ecto's DSL
- Manage database relationships and associations
- Handle data transformations with changesets
- Implement transactions for data consistency
- Work with PostgreSQL, MySQL, or other databases
- Build Phoenix applications with database access
- Create robust data validation layers
当你需要以下操作时,使用elixir-ecto-patterns:
- 构建基于数据库的Elixir应用
- 定义带验证的Schema和数据模型
- 使用Ecto的DSL编写复杂数据库查询
- 管理数据库关系与关联
- 用Changeset处理数据转换
- 实现事务以保证数据一致性
- 操作PostgreSQL、MySQL或其他数据库
- 构建带数据库访问的Phoenix应用
- 创建健壮的数据验证层
Best Practices
最佳实践
- Always use changesets for data validation
- Preload associations to avoid N+1 queries
- Use transactions for multi-step database operations
- Leverage Ecto.Multi for complex transaction logic
- Keep schemas focused and avoid god objects
- Use virtual fields for computed or temporary data
- Index foreign keys and frequently queried fields
- Use fragments for complex SQL when needed
- Write composable query functions
- Test database constraints and validations
- 始终使用Changeset进行数据验证
- 预加载关联以避免N+1查询问题
- 多步骤数据库操作使用事务
- 复杂事务逻辑使用Ecto.Multi
- 保持Schema聚焦,避免全能对象
- 虚拟字段用于计算或临时数据
- 为外键和频繁查询的字段创建索引
- 需要时使用fragments处理复杂SQL
- 编写可组合的查询函数
- 测试数据库约束与验证
Common Pitfalls
常见陷阱
- Not preloading associations (N+1 query problem)
- Forgetting to validate required fields
- Not using transactions for related operations
- Hardcoding queries instead of composing them
- Ignoring database constraints in schemas
- Not handling changeset errors properly
- Overusing embedded schemas for relational data
- Missing indexes on foreign keys
- Not using Repo.transaction for multi-step operations
- Exposing raw Ecto queries in business logic
- 未预加载关联(N+1查询问题)
- 忘记验证必填字段
- 关联操作未使用事务
- 硬编码查询而非组合查询
- 忽略Schema中的数据库约束
- 未正确处理Changeset错误
- 对关系型数据过度使用嵌入Schema
- 外键缺少索引
- 多步骤操作未使用Repo.transaction
- 业务逻辑中暴露原始Ecto查询