我有两张桌子。 topics
has_many
tweets
的表格。我的tweets
belongs_to
和topic
表。
主题架构:
defmodule Sentiment.Topic do
use Sentiment.Web, :model
schema "topics" do
field :title, :string
has_many :tweets, Sentiment.Tweet
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:title])
|> validate_required([:title])
end
end
推文架构:
defmodule Sentiment.Tweet do
use Sentiment.Web, :model
schema "tweets" do
field :message, :string
belongs_to :topic, Sentiment.Topic
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:message])
|> validate_required([:message])
end
end
我正在尝试在我的表格中插入topic
,然后在我对该主题进行Twitter搜索后,将tweets
插入Ecto.Multi
。
在我的控制器中,我使用protocol Enumerable not implemented for #Ecto.Changeset<action: nil, changes: %{message: "\"aloh....
对我的仓库操作进行分组,但是,每次运行我的操作时都会出现错误 def create(conn, %{"topic" => topic}) do
# create a topic changeset
topic_changeset = Topic.changeset(%Topic{}, topic)
# obtain a list of tweet messages: ["hello", "a tweet", "sup!"]
%{"title" => title} = topic
all_tweets = title
|> Twitter.search
# create an Ecto.Multi struct.
multi =
Ecto.Multi.new
|> Ecto.Multi.insert(:topics, topic_changeset) #insert topic
|> Ecto.Multi.run(:tweets, fn %{topics: topic} ->
changeset_tweets = all_tweets
|> Enum.map(fn(tweet) ->
%{topic_id: topic.id, message: tweet}
end)
Repo.insert_all(Tweet, changeset_tweets)
end)
# Run the transaction
case Repo.transaction(multi) do # ERROR HERE!
{:ok, result} ->
conn
|> put_flash(:info, "Success!")
|> redirect(to: topic_path(conn, :index))
{:error, :topics, topic_changeset, %{}} ->
conn
|> put_flash(:error, "Uh oh...")
|> render("new.html", changeset: topic_changeset)
{:error, :tweets, topic_changeset, %{}} ->
conn
|> put_flash(:error, "Something really bad happened...")
|>render("new.html", changeset: topic_changeset)
end
end
这就是我试图首先插入我的主题,获取它的id,然后在一个事务中插入带有相关id的推文消息。
insert_all
如何使用Ecto.Multi在一个事务中item_params["file"].path
|> File.stream!()
|> CSV.decode(separator: ?;, headers: [:level, :dim0, :dim1, :dim2, :dim3, :dim4])
|> Enum.map(fn (item) ->
{:ok, fields} = item
Item.changeset(%Item{}, %{topic_id: item_params["topic_id"], level: fields.level, dim0: fields.dim0, dim1: fields.dim1, dim2: fields.dim2, dim3: fields.dim3, dim4: fields.dim4})
|> Repo.insert
end)
大约500行?
更新 我已将变更集列表转换为地图列表,我的错误已更改为更令人困惑的内容。
答案 0 :(得分:9)
要让Ecto.Multi
正确推进步骤,每个人都必须返回{:ok, value}
或{:error, reason}
元组。
当insert
,update
或delete
变更集时,它会自动返回这样的元组,但对于run
,您需要明确地返回它。
请考虑以下事项:
Ecto.Multi.new
|> Ecto.Multi.insert(:topics, topic_changeset) #insert topic
|> Ecto.Multi.run(:tweets, fn %{topics: topic} ->
maps =
Enum.map(all_tweets, fn(tweet) ->
%{topic_id: topic.id, message: tweet}
end)
{count, _} = Repo.insert_all(Tweet, maps)
{:ok, count} # <----
end)
答案 1 :(得分:2)
Alex,这不是对Ecto.Multi
问题的直接回答,而是一个建议,即在主题变更集中使用cast_assoc(:tweets)
可能更容易。
这看起来像这样:
# Topic.ex
...
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:message])
|> cast_assoc(:tweets)
|> validate_required([:message])
end
# create_topic..
...
tweets = [%{message: "Tweet 1"}, %{message: "Tweet 2"}]
{:ok, topic} =
%Topic{}
|> Topic.changeset(Map.put(topic, :tweets, tweets))
|> Repo.insert()
答案 2 :(得分:1)
请注意,截至目前(2021 年)Ecto 3 已经提供了 Multi.insert_all
函数,因此在这种情况下不需要手动使用 Multi.run
。