使用 Ecto 一次插入多行。 "protocol Enumerable not implemented for #Ecto.Changeset"
Insert multiple rows at once with Ecto. "protocol Enumerable not implemented for #Ecto.Changeset"
我有两个 table。 topics
中的 table,其中 has_many
tweets
。我的 table 的 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
我试图在我的 table 中插入一个 topic
,然后在我 运行 推特搜索该主题后插入 500 tweets
。
在我的控制器中,我使用 Ecto.Multi
对我的回购操作进行分组,但是,每次我 运行 我的操作都会出现错误 protocol Enumerable not implemented for #Ecto.Changeset<action: nil, changes: %{message: "\"aloh....
这就是我尝试首先插入我的主题、获取它的 ID,然后插入带有与一个事务关联的 ID 的推文消息的方式。
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
如何使用 Ecto.Multi 在一个事务中 insert_all
大约 500 行?
更新
我已将变更集列表转换为地图列表,我的错误已变得更加混乱。
为了 Ecto.Multi
正确地进行步骤,每个步骤都必须 return {:ok, value}
或 {:error, reason}
元组。
当insert
ing、update
ing 或delete
ing 一个变更集时,它会自动return 这样一个元组,但是对于run
,你需要明确 return 它。
请考虑以下事项:
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)
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()
请注意,截至目前(2021 年),Ecto 3 已经提供了 Multi.insert_all
功能,因此在这种情况下无需手动使用 Multi.run
。
我有两个 table。 topics
中的 table,其中 has_many
tweets
。我的 table 的 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
我试图在我的 table 中插入一个 topic
,然后在我 运行 推特搜索该主题后插入 500 tweets
。
在我的控制器中,我使用 Ecto.Multi
对我的回购操作进行分组,但是,每次我 运行 我的操作都会出现错误 protocol Enumerable not implemented for #Ecto.Changeset<action: nil, changes: %{message: "\"aloh....
这就是我尝试首先插入我的主题、获取它的 ID,然后插入带有与一个事务关联的 ID 的推文消息的方式。
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
如何使用 Ecto.Multi 在一个事务中 insert_all
大约 500 行?
更新 我已将变更集列表转换为地图列表,我的错误已变得更加混乱。
为了 Ecto.Multi
正确地进行步骤,每个步骤都必须 return {:ok, value}
或 {:error, reason}
元组。
当insert
ing、update
ing 或delete
ing 一个变更集时,它会自动return 这样一个元组,但是对于run
,你需要明确 return 它。
请考虑以下事项:
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)
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()
请注意,截至目前(2021 年),Ecto 3 已经提供了 Multi.insert_all
功能,因此在这种情况下无需手动使用 Multi.run
。