在为现有模型添加新关联时,我对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. 儿童必须在更新时给予。默认情况下,不可能 孤儿嵌入或相关记录,试图这样做导致 此错误消息。
所以这里的事情让我感到困惑(有一些):
User
和Dicussion_item
来添加类似的内容?答案 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_id
和discussion_item_id
。
Ecto.Multi可能是在单个交易中创建%Like{}
和%DiscussionItemLike{}
的简洁方法。
Multi.new()
|> Multi.insert(insert_like_changeset(params))
|> Multi.insert(insert_discussion_item_like_changeset(params))
|> Repo.transaction()