Insert_all protocol Enumerable not implemented

I created a simple Post schema with a title (string) and tags (json array of string).

defmodule JsonbDemo.Post do
  use Ecto.Schema

  schema "posts" do
    field :title, :string
    field :tags, {:array, :string}
  end
end

# migration
defmodule JsonbDemo.Repo.Migrations.AddPostsD do
  use Ecto.Migration

  def change do
    create table(:posts) do
      add(:title, :string)
      add(:tags, :jsonb)
    end
  end
end

I can insert Post object using Repo.insert, but when I try to pass post into Repo.insert_all I’ve got Protocol.UndefinedError. What did I do wrong?

iex(1)> p = %JsonbDemo.Post{title: "Jedi", tags: ["a", "b"]}
%JsonbDemo.Post{
  __meta__: #Ecto.Schema.Metadata<:built, "posts">,
  id: nil,
  title: "Jedi",
  tags: ["a", "b"]
}
iex(2)> JsonbDemo.Repo.insert(p)
[debug] QUERY OK db=13.9ms decode=1.1ms queue=1.2ms idle=190.2ms
INSERT INTO "posts" ("tags","title") VALUES ($1,$2) RETURNING "id" [["a", "b"], "Jedi"]
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
{:ok,
 %JsonbDemo.Post{
   __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
   id: 1,
   title: "Jedi",
   tags: ["a", "b"]
 }}
iex(3)> JsonbDemo.Repo.insert_all(JsonbDemo.Post, [p])
** (Protocol.UndefinedError) protocol Enumerable not implemented for %JsonbDemo.Post{__meta__: #Ecto.Schema.Metadata<:built, "posts">, id: nil, title: "Jedi", tags: ["a", "b"]} of type JsonbDemo.Post (a struct). This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Stream
    (elixir 1.14.3) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.14.3) lib/enum.ex:166: Enumerable.reduce/3
    (elixir 1.14.3) lib/enum.ex:4307: Enum.map_reduce/3
    (ecto 3.10.1) lib/ecto/repo/schema.ex:87: anonymous fn/5 in Ecto.Repo.Schema.extract_header_and_fields/8
    (elixir 1.14.3) lib/enum.ex:1780: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (ecto 3.10.1) lib/ecto/repo/schema.ex:86: Ecto.Repo.Schema.extract_header_and_fields/8
    (ecto 3.10.1) lib/ecto/repo/schema.ex:48: Ecto.Repo.Schema.do_insert_all/7

However, the weir thing is if I pass raw struct to Repo.insert_all it works.

iex(3)> JsonbDemo.Repo.insert_all(JsonbDemo.Post, [%{title: "Mario", tags: ["one", "two"]}])
[debug] QUERY OK db=8.1ms queue=1.2ms idle=588.5ms
INSERT INTO "posts" ("tags","title") VALUES ($1,$2) [["one", "two"], "Mario"]
↳ :erl_eval.do_apply/6, at: erl_eval.erl:685
{1, nil}

Hi @csokun this is expected and documented, insert_all only takes simple maps Ecto.Repo — Ecto v3.10.1 not structs.

2 Likes

I see thanks @benwilson512