Elixir Ecto中embeds_one如何关联多个不同的embedded schema
错误原因
Ecto 原生的embeds_one并不支持either参数,你看到的相关用法属于非官方的野路子实现,官方文档从未收录该参数。你将either: [ Product, Service]传给了embeds_one本该填写embedded schema模块的位置,Ecto运行时会把这个keyword列表当做模块调用内置方法,自然就触发了你遇到的参数错误。
正确实现方式
不需要依赖不存在的either参数,手动处理动态类型的embedded schema逻辑即可,完整实现如下:
1. 调整Schema定义
schema "items" do field :name, :string # 用枚举约束type的取值范围,比普通字符串更安全 field :type, Ecto.Enum, values: [:product, :service, :task] # 直接定义details为map类型,不绑定固定embedded schema field :details, :map # 可选:加after_load钩子,读取数据时自动把details转为对应struct after_load :load_details_struct end
2. 实现动态cast逻辑
首先确保你的Product/Service/Task三个embedded schema已经定义好各自的changeset验证规则,然后在Item模块补充如下方法:
@doc false def changeset(item, attrs) do item |> cast(attrs, [:name, :type]) |> validate_required([:name, :type]) |> then(fn changeset -> # 拿cast后的type值,避免直接取attrs时字符串key/原子key不匹配的问题 current_type = get_change(changeset, :type) || changeset.data.type details_attrs = attrs[:details] || attrs["details"] cast_dynamic_details(changeset, current_type, details_attrs) end) end # 动态cast不同类型的details defp cast_dynamic_details(changeset, _type, nil), do: changeset defp cast_dynamic_details(changeset, type, details_attrs) do # 根据type匹配对应的embedded schema details_schema = case type do :product -> Product :service -> Service :task -> Task end # 处理新建/更新场景的初始值 existing_details = changeset.data.details || struct(details_schema) details_changeset = details_schema.changeset(existing_details, details_attrs) if details_changeset.valid? do put_change(changeset, :details, apply_changes(details_changeset)) else # 把embedded schema的错误合并到父changeset Enum.reduce(details_changeset.errors, changeset, fn {field, {msg, opts}}, acc -> add_error(acc, :details, "#{field} #{msg}", opts) end) end end # after_load钩子实现,读取时自动转struct defp load_details_struct(item) do details_schema = case item.type do :product -> Product :service -> Service :task -> Task end %{item | details: struct(details_schema, item.details)} end
可选优化
如果需要保持embeds_one的相关API能力,可以自定义一个Ecto类型来封装动态类型匹配逻辑,不需要修改上层调用规则。
内容的提问来源于stack exchange,提问作者sooon
相关产品推荐
相关产品推荐

