Multi.insert_all/Repo.insert_all fail with Enumerable not implemented

I tried to use the insert_all functions to batch insert 2 records, but it always fails with Enumerable not implemented for Model:

Repo.insert_all(Model, [%Model{id: 1, friend_id: 4}, %Model{id: 4, friend_id: 1}])

I got it working by inserting every record at once:

Multi.new
|> Multi.insert(:model1, %Model{id: 1, friend_id: 4})
|> Multi.insert(:model2, %Model{id: 4, friend_id: 1})
|> Repo.transaction

Maybe my misunderstanding is that only normal maps are supported?

Repo.insert_all(Model, [%{id: 1, friend_id: 4}, %{id: 4, friend_id: 1}])

Unfortunately, then the timestamp values are not automatically set:
null value in column "inserted_at" violates not-null constraint

Is there any way to achieve the batch insertion with phoenix models and what exactly is my mistake or misunderstanding?

1 Like

You can insert your data with timestamps like so

to_insert =
  [%{id: 1, friend_id: 4}, %{id: 4, friend_id: 1}]
  |> Enum.map(&Map.put(&1, :inserted_at, NaiveDateTime.utc_now())) 

Repo.insert_all(Model, to_insert)

Yeah, but without knowing the implementation reasons, I don’t really find this intuitive and comfortable. I would have expected to be able to use it like the insert function.

The reason for this is that it’s supposed to be a bit lower level than Repo.insert, so that it accepts table names as the first argument, Repo.insert_all("users", [...]). And it doesn’t need schemas to be defined, you just pass the maps with some data. That’s why it doesn’t know that it needs to add a inserted_at timestamp.

It looks like only lists of maps or keyword lists are supported. From the @spec:

entries :: [map | Keyword.t]

There is some explanation in the documentation about why this is the case:

However any other autogenerated value, like timestamps, won’t be autogenerated when using insert_all/3. This is by design as this function aims to be a more direct way to insert data into the database without the conveniences of insert/2. This is also consistent with update_all/3 that does not handle timestamps as well.

It is also not possible to use insert_all to insert across multiple tables, therefore associations are not supported.

It does feel like a shortcoming though, I’m not sure why it doesn’t work with schemas/structs.

EDIT: You could create a utility around Multi like this:

def insert_multiple(entries) do
  multi =
    Enum.reduce entries, Multi.new(), fn(multi, entry) ->
      Multi.insert(multi, entry.id, entry)
    end
  Repo.transaction(multi)
end

It depends on the struct to have an id field for the multi name, but you could change that to anything I think, it just needs to be unique.

3 Likes