重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
elixir-ecto-patterns by thebushidocollective/han
npx skills add https://github.com/thebushidocollective/han --skill elixir-ecto-patterns掌握 Ecto,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
end
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
end
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import Ecto.Query
# 获取所有用户
Repo.all(User)
# 通过 ID 获取用户
Repo.get(User, 1)
Repo.get!(User, 1) # 未找到时抛出异常
# 通过特定字段获取
Repo.get_by(User, email: "user@example.com")
# 使用 where 子句过滤
query = from u in User, where: u.age > 18
Repo.all(query)
# 选择特定字段
query = from u in User, select: {u.id, u.name}
Repo.all(query)
# 排序结果
query = from u in User, order_by: [desc: u.inserted_at]
Repo.all(query)
# 限制和偏移
query = from u in User, limit: 10, offset: 20
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)
# 使用管道语法
User
|> where([u], u.is_active == true)
|> where([u], u.age >= 18)
|> order_by([u], desc: u.inserted_at)
|> limit(10)
|> Repo.all()
# 动态查询
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
# 预加载关联
user = Repo.get(User, 1) |> Repo.preload(:posts)
# 预加载嵌套关联
user = Repo.get(User, 1) |> Repo.preload([posts: :comments])
# 带预加载的查询
query = from u in User, preload: [:posts, :organization]
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)
# 连接和预加载
query =
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.all(query)
# 统计记录数
Repo.aggregate(User, :count)
# 带条件的统计
query = from u in User, where: u.is_active == true
Repo.aggregate(query, :count)
# 其他聚合操作
Repo.aggregate(User, :avg, :age)
Repo.aggregate(User, :sum, :age)
Repo.aggregate(User, :max, :age)
# 分组
query =
from u in User,
group_by: u.role,
select: {u.role, count(u.id)}
Repo.all(query)
# 带 having 的分组
query =
from u in User,
group_by: u.role,
having: count(u.id) > 5,
select: {u.role, count(u.id)}
Repo.all(query)
# 使用变更集插入
attrs = %{name: "John", email: "john@example.com", age: 30}
%User{}
|> User.changeset(attrs)
|> Repo.insert()
# 不使用变更集插入
Repo.insert(%User{name: "Jane", email: "jane@example.com"})
# 更新
user = Repo.get(User, 1)
user
|> User.changeset(%{age: 31})
|> Repo.update()
# 更新所有
query = from u in User, where: u.is_active == false
Repo.update_all(query, set: [is_active: true])
# 删除
user = Repo.get(User, 1)
Repo.delete(user)
# 删除所有
query = from u in User, where: u.is_active == false
Repo.delete_all(query)
# 基础事务
Repo.transaction(fn ->
user = Repo.insert!(%User{name: "Alice"})
Repo.insert!(%Post{title: "First post", user_id: user.id})
end)
# 用于复杂事务的 Multi
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()
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
end
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
# 加密逻辑
value
end
defp decrypt(value) do
# 解密逻辑
value
end
end
# 在模式中的使用
schema "users" do
field :secret, MyApp.Encrypted
end
在以下情况下使用 elixir-ecto-patterns:
每周安装数
64
代码仓库
GitHub 星标数
128
首次出现
2026年1月22日
安全审计
安装于
opencode56
gemini-cli53
codex53
github-copilot47
cursor45
claude-code43
Master Ecto, Elixir's database wrapper and query generator. This skill covers schemas, changesets, queries, associations, and transactions for building robust database applications.
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
end
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
end
import Ecto.Query
# Get all users
Repo.all(User)
# Get user by ID
Repo.get(User, 1)
Repo.get!(User, 1) # Raises if not found
# Get by specific field
Repo.get_by(User, email: "user@example.com")
# Filter with where clause
query = from u in User, where: u.age > 18
Repo.all(query)
# Select specific fields
query = from u in User, select: {u.id, u.name}
Repo.all(query)
# Order results
query = from u in User, order_by: [desc: u.inserted_at]
Repo.all(query)
# Limit and offset
query = from u in User, limit: 10, offset: 20
Repo.all(query)
# 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)
# 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()
# 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
# Preload associations
user = Repo.get(User, 1) |> Repo.preload(:posts)
# Preload nested associations
user = Repo.get(User, 1) |> Repo.preload([posts: :comments])
# Query with preload
query = from u in User, preload: [:posts, :organization]
Repo.all(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)
# Join and preload
query =
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.all(query)
# Count records
Repo.aggregate(User, :count)
# Count with condition
query = from u in User, where: u.is_active == true
Repo.aggregate(query, :count)
# Other aggregations
Repo.aggregate(User, :avg, :age)
Repo.aggregate(User, :sum, :age)
Repo.aggregate(User, :max, :age)
# Group by
query =
from u in User,
group_by: u.role,
select: {u.role, count(u.id)}
Repo.all(query)
# 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)
# Insert with changeset
attrs = %{name: "John", email: "john@example.com", age: 30}
%User{}
|> User.changeset(attrs)
|> Repo.insert()
# Insert without changeset
Repo.insert(%User{name: "Jane", email: "jane@example.com"})
# Update
user = Repo.get(User, 1)
user
|> User.changeset(%{age: 31})
|> Repo.update()
# Update all
query = from u in User, where: u.is_active == false
Repo.update_all(query, set: [is_active: true])
# Delete
user = Repo.get(User, 1)
Repo.delete(user)
# Delete all
query = from u in User, where: u.is_active == false
Repo.delete_all(query)
# Basic transaction
Repo.transaction(fn ->
user = Repo.insert!(%User{name: "Alice"})
Repo.insert!(%Post{title: "First post", user_id: user.id})
end)
# 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()
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
end
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
end
# Usage in schema
schema "users" do
field :secret, MyApp.Encrypted
end
Use elixir-ecto-patterns when you need to:
Weekly Installs
64
Repository
GitHub Stars
128
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode56
gemini-cli53
codex53
github-copilot47
cursor45
claude-code43
Kotlin Exposed ORM 模式指南:DSL查询、DAO、事务管理与生产配置
1,200 周安装