如何在Ecto连接表中关联两个现有模型?解决必填校验报错
Ecto多对多关联保存时出现「can't be blank」错误的解决方法
问题背景
我有Practitioner和Product两个模型,二者为多对多关系,通过ProductPractitioners连接表关联,但保存关联时出现can't be blank错误,请问关联两个现有实体到连接表的正确方式是什么?
模型代码
products_practitioners
10 schema "products_practitioners" do 11 belongs_to :product, Product 12 belongs_to :practitioner, Practitioner 13 14 timestamps() 15 end 18 def changeset(products_practitioners, attrs) do 19 products_practitioners 20 # TODO: Don´t accept invalid params here 21 |> cast(attrs, []) 22 |> validate_required([:product, :practitioner]) 23 end
products
10 schema "products" do 11 field :name, :string 12 many_to_many :practitioners, Practitioner, join_through: ProductsPractitioners 13 14 timestamps() 15 end
practitioners
11 schema "practitioners" do 12 field :first_name, :string 13 field :last_name, :string 14 15 has_one :calendar, Calendar 16 many_to_many :products, Product, join_through: ProductsPractitioners 17 18 timestamps() 19 end
创建实体的IEX操作
iex(11)> {:ok, product} = Factory.product_factory |> Booking.Repo.insert iex(12)> {:ok, practictioner} = Factory.practitioner_factory |> Booking.Repo.insert iex(13)> attrs = %{product: product, practictioner: practictioner} %{ practictioner: %Booking.Practitioners.Practitioner{ __meta__: #Ecto.Schema.Metadata<:loaded, "practitioners">, id: "52dc8f9e-d3b4-4ce5-9016-6a3ef07679e2", first_name: "Judd", last_name: "Klocko", calendar: #Ecto.Association.NotLoaded<association :calendar is not loaded>, products: #Ecto.Association.NotLoaded<association :products is not loaded>, inserted_at: ~N[2022-12-10 09:31:21], updated_at: ~N[2022-12-10 09:31:21] }, product: %Booking.Products.Product{ __meta__: #Ecto.Schema.Metadata<:loaded, "products">, id: "bcb98f0e-e5a2-40cb-bd9a-0eac62ddf0a8", name: "qui", practitioners: #Ecto.Association.NotLoaded<association :practitioners is not loaded>, inserted_at: ~N[2022-12-10 09:31:40], updated_at: ~N[2022-12-10 09:31:40] } }
尝试关联的IEX操作及错误
iex(14)> %ProductsPractitioners{}|> ProductsPractitioners.changeset(attrs)|> Repo.insert() {:error, #Ecto.Changeset< action: :insert, changes: %{}, errors: [ product: {"can't be blank", [validation: :required]}, practitioner: {"can't be blank", [validation: :required]} ], data: #Booking.Products.ProductsPractitioners<>, valid?: false >}
错误原因及解决方案
错误原因
- Changeset字段配置错误:连接表的
belongs_to关联对应的数据库字段是product_id和practitioner_id,但原changeset的cast方法未包含这两个字段,导致传入参数无法被解析;同时validate_required检查的是关联名:product、:practitioner,而非实际存储的字段名,不符合Ecto验证逻辑。 - 参数传递方式错误:直接传入完整模型结构体,而非关联对应的ID字段,结合错误的changeset配置,最终导致验证失败。
正确解决步骤
步骤1:修复连接表的Changeset
修改products_practitioners的changeset方法,将实际数据库字段加入cast和validate_required:
def changeset(products_practitioners, attrs) do products_practitioners |> cast(attrs, [:product_id, :practitioner_id]) |> validate_required([:product_id, :practitioner_id]) end
步骤2:两种关联实现方式
方式一:直接插入连接表记录
传递实体的ID参数到连接表:
attrs = %{product_id: product.id, practitioner_id: practictioner.id} %ProductsPractitioners{} |> ProductsPractitioners.changeset(attrs) |> Repo.insert()
方式二:通过Ecto多对多关联方法操作(推荐)
利用Ecto提供的put_assoc方法,通过主模型管理关联,更符合Ecto关联设计:
# 通过Product模型添加关联的Practitioner product |> Ecto.Changeset.change() |> Ecto.Changeset.put_assoc(:practitioners, [practictioner]) |> Repo.update() # 若要添加多个关联,扩展列表即可: # |> Ecto.Changeset.put_assoc(:practitioners, [practictioner1, practictioner2])
内容的提问来源于stack exchange,提问作者martins
相关产品推荐
相关产品推荐

