使用Ecto一次插入多行。 “协议可枚举未针对#Ecto.Changeset实施”

时间:2017-07-14 19:10:18

标签: elixir phoenix-framework ecto

我有两张桌子。 topics has_many tweets的表格。我的tweets belongs_totopic表。

主题架构:

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行?

更新 我已将变更集列表转换为地图列表,我的错误已更改为更令人困惑的内容。

error what I am trying to insert

3 个答案:

答案 0 :(得分:9)

要让Ecto.Multi正确推进步骤,每个人都必须返回{:ok, value}{:error, reason}元组。

insertupdatedelete变更集时,它会自动返回这样的元组,但对于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