mcpField
E
ecto-patterns

ecto-patterns

github.com/oliver-kriska开发57155 次安装1 周前更新
代码仓库

关于 ecto-patterns

供开发者使用的 Ecto 模式参考,涵盖 schema、查询和迁移。它归纳安全的数据处理、查询与预加载选择、数据库约束原则及常见反模式,并链接到详细参考资料。

主要功能

  1. 01Schema 与 changeset 指引
  2. 02安全查询与预加载模式
  3. 03迁移和约束原则
  4. 04常见反模式示例

适用场景

  1. 01选择 cast 或内部变更函数
  2. 02避免查询中的 SQL 注入
  3. 03规划集合预加载
  4. 04检查金额字段和关联关系

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix --skill ecto-patterns

安装后在 Claude Code 中输入/ecto-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
changesets.md
fulltext-search.md
migrations.md
queries.md
transactions.md

详细介绍

Ecto 模式参考

Ecto 架构、查询和迁移的参考指南。

铁律——绝不可违反

  1. 变更集用于外部数据——对用户/API 输入使用 cast/4,对内部可信数据使用 change/2 或 put_change/3
  2. 绝不使用 :float 表示金额——始终使用 :decimal 或 :integer(分)
  3. 不要使用 Rails 风格的多态关联——这会破坏外键约束;请使用多个可空外键或单独的联结表
  4. 查询中始终使用绑定值——u.name == ^user_input 是安全的,字符串插值会导致 SQL 注入
  5. 预加载集合,而非单个记录——在循环中预加载会产生 N+1 查询
  6. 发生竞态条件时,约束优于验证——验证可快速反馈,约束可提供数据库级安全保障
  7. has_many 使用单独查询,belongs_to 使用 JOIN——避免行数膨胀
  8. 不要隐式执行交叉联结——from(a in A, b in B) 不带 on: 会创建笛卡尔积
  9. 使用共享数据时,在 cast_assoc 前去重——多个父项共享子项数据时,构建变更集前要先对子项记录去重。去重仅在单个变更集内有效

快速架构模板

defmodule MyApp.Context.Entity do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "entities" do
    field :name, :string
    field :status, Ecto.Enum, values: [:draft, :active, :archived]
    field :amount_cents, :integer  # Never :float for money!
    belongs_to :user, MyApp.Accounts.User
    timestamps(type: :utc_datetime_usec)
  end

  def changeset(entity, attrs) do
    entity
    |> cast(attrs, [:name, :status, :amount_cents])
    |> validate_required([:name])
    |> foreign_key_constraint(:user_id)
  end
end

快速决策

cast、put_change 与 change
函数适用场景
cast/4外部数据(用户输入、API)
put_change/3内部可信数据(时间戳、计算结果)
change/2来自现有结构体的内部数据
预加载策略
关联关系策略
belongs_toJOIN(单次查询)
has_many单独查询(避免行数膨胀)

常见反模式

错误做法正确做法
field :amount, :floatfield :amount_cents, :integer
"SELECT * WHERE name = '#{name}'"from(u in User, where: u.name == ^name)
Repo.all(User) |> Enum.filter(& &1.active)from(u in User, where: u.active)
在循环中预加载Repo.preload(posts, :comments)
对用户输入使用 Repo.get!(User, user_id)Repo.get(User, id) + 处理 nil
在 Repo.transaction 中使用 {:ok, _} = Repo.update(cs)使用 case/with + Repo.rollback(cs)(参见 transactions.md)

参考资料

有关详细模式,请参阅:

  • ${CLAUDE_SKILL_DIR}/references/changesets.md - cast 与 put_change、自定义验证、prepare_changes
  • ${CLAUDE_SKILL_DIR}/references/queries.md - 可组合查询、dynamic、子查询、预加载
  • ${CLAUDE_SKILL_DIR}/references/migrations.md - 安全迁移、并发索引、NOT NULL
  • ${CLAUDE_SKILL_DIR}/references/transactions.md - Repo.transact、Ecto.Multi、upsert

评论

支持 Markdown