微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

归属地关联的 Ecto 变更集 我尝试过的

如何解决归属地关联的 Ecto 变更集 我尝试过的

我正在尝试在 Ecto 内部复制我在 Rails 中习惯的行为。在 Rails 中,如果我有 ParentChild 模型,并且 Child 属于 Parent,我可以这样做:Child.create(parent: parent)。这会将 parent_idChild 属性分配给 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 举报,一经查实,本站将立刻删除。