如何解决归属地关联的 Ecto 变更集 我尝试过的
我正在尝试在 Ecto 内部复制我在 Rails 中习惯的行为。在 Rails 中,如果我有 Parent
和 Child
模型,并且 Child
属于 Parent
,我可以这样做:Child.create(parent: parent)
。这会将 parent_id
的 Child
属性分配给 parent
的 ID。
这是我最小的 Ecto 示例:
defmodule Example.Parent do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id,:binary_id,autogenerate: true}
@foreign_key_type :binary_id
schema "parent" do
has_many :children,Example.Child
end
def changeset(parent,attributes) do
parent |> cast(attributes,[])
end
end
defmodule Example.Child do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id,autogenerate: true}
@foreign_key_type :binary_id
schema "child" do
belongs_to :parent,Example.Parent
end
def changeset(child,attributes) do
child
|> cast(attributes,[:parent_id])
end
end
这是我想要的行为示例:
parent = %Example.Parent{id: Ecto.UUID.generate()}
changeset = Example.Child.changeset(%Example.Child{},%{parent: parent})
# This should be the parent's ID!
changeset.changes.parent_id
我尝试过的
我尝试了几种不同的方法来让它在 Ecto 中发挥作用,但我一直在做空。
child
|> cast(attributes,[:parent_id])
|> put_assoc(:parent,attributes.parent)
这似乎没有分配关联。
我尝试直接投射关联:
child
|> cast(attributes,[:parent_id,:parent])
但这会产生一个 RuntimeError
告诉我使用 cast_assoc/3
。这似乎不是我想要的,但我还是尝试了。
child
|> cast(attributes,[:parent_id])
|> cast_assoc(:parent,with: &Example.Parent.changeset/2)
这会产生一个 Ecto.CastError
。
最后,我尝试从 :with
中删除 cast_assoc/3
选项。
child
|> cast(attributes,[:parent_id])
|> cast_assoc(:parent)
但我遇到了同样的错误。
解决方法
对于内置的 Ecto 函数,这似乎是不可能的。为了启用它,我写了自己的:
defmodule Example.Schema do
@moduledoc """
This module contains schema helpers which can be mixed into any schema. In addition,it also
automatically sets the ID type to a UUID,and uses and imports the standard Ecto modules.
"""
defmacro __using__(_options) do
quote do
use Ecto.Schema
import Ecto.Changeset
@doc """
Allows an association to be assigned to a changeset from the changeset's data with minimal fuss.
You can either assign the association's ID attribute,or assign the association struct directly.
"""
def assign_assoc(changeset,attributes = %{},name) do
name_string = to_string(name)
name_atom = String.to_existing_atom(name_string)
id_string = "#{name_string}_id"
id_atom = String.to_existing_atom(id_string)
cond do
Map.has_key?(attributes,name_string) ->
put_assoc(changeset,name_atom,attributes[name_string])
Map.has_key?(attributes,name_atom) ->
put_assoc(changeset,attributes[name_atom])
Map.has_key?(attributes,id_string) ->
put_change(changeset,id_atom,attributes[id_string])
Map.has_key?(attributes,id_atom) ->
put_change(changeset,attributes[id_atom])
true ->
changeset
end
end
@doc """
Validates that the given association is present either in the changeset's changes or its data.
"""
def validate_assoc_required(changeset,name) do
# NOTE: The name value doesn't use `get_field` because that produces an error when the
# association isn't loaded.
id_value = get_field(changeset,:"#{name}_id")
name_value = get_change(changeset,name) || Map.get(changeset.data,name)
has_id? = id_value != nil
has_value? = name_value != nil && Ecto.assoc_loaded?(name_value)
unless has_id? || has_value? do
add_error(changeset,name,"is required")
else
changeset
end
end
end
end
end
这两个函数使得将 belongs_to
关联添加到变更集变得非常容易。
def changeset(child,attributes) do
child
|> cast(attributes,[])
|> assign_assoc(attributes,:parent)
|> validate_assoc_required(:parent)
end
这种方法让您可以随意分配关联。两种形式都适用于 Repo.insert
。
Example.Child.changeset(%Example.Child{},%{parent: parent})
Example.Child.changeset(%Example.Child{},%{parent_id: parent.id})
Example.Child.changeset(%Example.Child{parent: parent},%{parent: nil})
Example.Child.changeset(%Example.Child{parent_id: parent_id},%{parent_id: nil})
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。