首页 > 解决方案 > 为整个协会构建变更集

问题描述

我在这里读到了cast_assoc/3。但是文档看起来很混乱。我想single changeset for the entire association在一个事务中构建并执行它以进行更新。这是我的模型;

    defmodule User do      
     use Gallery.Web, :model


     schema "users" do   
       field(:name, :string)
       field(:occupation, :string)
       has_many(:paintings, Painting)
     end

    def changeset(struct, params \\ %{}) do
     struct
     |> cast(params, [ :name, :occupation ])
     |> validate_required([:name, :occupation]) 
    end
   end

   defmodule Painting do      
     use Gallery.Web, :model


     schema "paintings" do   
       field(:name, :string)          
       belongs_to(:users, User)
     end

    def changeset(struct, params \\ %{}) do
     struct
     |> cast(params, [ :name ])
     |> validate_required([:name]) 
    end
   end

这是我要构建的单个变更集的数据

data= %User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   id: 4606,
   name: "Test",
   occupation: "Artist",
   paintings: [
     %Painting{
     __meta__: #Ecto.Schema.Metadata<:loaded, "paintings">,
     user_id: 4606,
     id: 1515,
     name: "philip"
     },
   %Painting{
    __meta__: #Ecto.Schema.Metadata<:loaded, "paintings">,
    user_id: 4606,
    id: 1516,
    name: "john"
    }
  ]
 }

有什么建议么?

谢谢

标签: elixirecto

解决方案


要使变更集正常工作,您的数据需要是纯映射而不是结构(就好像您从参数中获取的一样)。

如果您只想插入具有多幅绘画的用户,您需要:

  • 摆脱结构
  • 摆脱 ids(在插入的情况下,它们是动态创建的)
  • 有一个cast_assoc用户变更集

像这样:

data = %{
  name: "Test",
  occupation: "Artist",
  paintings: [
    %{
      name: "philip"
    },
    %{
      name: "john"
    }
  ]
}

%User{}
|> User.changeset(data)
|> Repo.insert

如果您还想以这种方式更新内容,它会变得更加复杂。目前尚不清楚中的绘画列表是否data应该更新现有的绘画,添加新的或删除所有以前的绘画并将它们替换为data. 我个人不建议使用嵌套变更集进行更新。https://hexdocs.pm/ecto/Ecto.Changeset.html#cast_assoc/3

澄清后更新:

要更新所有到位的绘画,您还需要做两件事。你需要:

  • 预加载画作
  • 在数据中有绘画ID

像这样:

data = %{
  name: "Test",
  occupation: "Artist",
  paintings: [
    %{
      id: 1,
      name: "philip"
    },
    %{
      id: 2,
      name: "john"
    }
  ]
}

User
|> Repo.get_by(id: user_id)
|> Repo.preload(:paintings)
|> User.changeset(data)
|> Repo.update

你不需要使用Multi. 这将是一笔交易。使用Repo一次模块通常表示一次数据库操作。

所有的魔法都发生在paintings: [...]. 根据文档,您有四种情况:

  • 如果参数不包含ID,则参数数据将通过新结构传递到changeset/2,成为插入操作
  • 如果参数包含一个 ID 并且没有与该 ID 关联的子节点,则参数数据将通过新结构传递到 changeset/2 并成为插入操作
  • 如果参数包含一个 ID 并且有一个与该 ID 关联的子节点,则参数数据将使用现有结构传递到 changeset/2 并成为更新操作
  • 如果有一个带有 ID 的关联子节点并且其 ID 未作为参数给出,则将调用该关联的 :on_replace 回调(请参阅模块文档中的“替换时”部分)

您对第三种情况下的更新感兴趣。如果你没有通过所有的画,data你可能还会对第四个感兴趣。


推荐阅读