Ecto put_assoc为什么要预加载以及为什么需要id

时间:2016-12-02 19:56:27

标签: elixir phoenix-framework ecto

在为现有模型添加新关联时,我对Ecto感到相当困惑。让我们说我有一些用户可以喜欢帖子的讨论板,我有以下模型:

  schema "discussion_items" do
    many_to_many :likes, XXX.Tag, join_through: "discussion_items_likes"
    timestamps()
  end

  schema "likes" do
    belongs_to :user, XXX.User
    timestamps()
  end

现在假设我有一个现有的DiscussionItem,我想添加一个类似的关联,这里是我尝试(简化):

user = Repo.one(User)
discussion_item = Repo.one(from d in DiscussionItem, preload [:likes])

like_changeset = Like.changeset(%Like{})
                     |> Ecto.Changeset.put_assoc(:user,   user)
changeset = Ecto.Changeset.change(discussion_item) 
                |>  Ecto.Changeset.put_assoc(:likes, [like_changeset])

Ecto.update!(changeset)

我发现这会在第一时间正确创建Like和关联,但之后会抱怨我需要以某个id的形式提供其他信息:

  

你试图改变关系:喜欢XXX.DiscussionItem,   但是缺少数据

     

如果您尝试更新现有条目,请确保   您在数据旁边包含条目主键(ID)。

     

如果你与很多孩子有关系,至少相同的N.   儿童必须在更新时给予。默认情况下,不可能   孤儿嵌入或相关记录,试图这样做导致   此错误消息。

所以这里的事情让我感到困惑(有一些):

  1. 此错误消息似乎表明,为了更新一个关联的项目,我需要提供包含所有项目的更改集,包括更改的项目。这看起来很疯狂,所以我猜测我读错了。
  2. 我不想更新喜欢的关联 - 我想添加一个。 put_assoc在这里是错误的选择吗?
  3. 我是否真的需要预装喜欢只是为了插入一个项目?即使在更新时,我也不想加载整个关联集合来更改一个。
  4. 我是否需要加载UserDicussion_item来添加类似的内容?

1 个答案:

答案 0 :(得分:3)

  

此错误消息似乎表明,为了更新一个关联的项目,我需要提供包含所有项目的更改集,包括更改的项目。这看起来很疯狂,所以我猜我读错了。

是的,你需要预加载。来自put_assoc文档:

  

此函数要求在changeset结构中预先加载关联。缺少数据将调用关联上定义的:on_replace行为。

文档中给出的示例:

# We can associate at any time post and tags together using changesets
post
|> Repo.preload(:tags) # Load existing data
|> Ecto.Changeset.change() # Build the changeset
|> Ecto.Changeset.put_assoc(:tags, [tag]) # Set the association
|> Repo.update!
  

我不想更新喜欢的关联 - 我想添加一个。 put_assoc在这里是错误的选择吗?

只有当您不介意预加载所有现有put_assoc时,

likes才可以。如果你想避免预加载,那么Join Schema似乎是最好的选择。

  

我是否真的需要预加载喜欢只是为了插入一个项目?即使在更新时,我也不想加载整个关联集合来改变它。

为方便使用put_assoc,您需要预加载。 另一种方法是使用Join Schema,允许您独立于实体的任何一方显式插入/更新关系。

  

我是否需要加载User和Dicussion_item来添加类似的内容?

不,你只需要ID。 您可以使用参数中填充的Like字段构建user_id变更集。

同样,使用DiscussionItemLikes联接架构,您可以设置like_iddiscussion_item_id

Ecto.Multi可能是在单个交易中创建%Like{}%DiscussionItemLike{}的简洁方法。

Multi.new()
|> Multi.insert(insert_like_changeset(params))
|> Multi.insert(insert_discussion_item_like_changeset(params))
|> Repo.transaction()