Doing Bulk Insertions with Embedded Schemas with Ecto.Repo.insert_all

Last updated on May 31, 2020

You can perform bulk insertions for records that contain embedded schemas by doing the following:


alias MyApp.MyEmbed
alias MyApp.MyMainSchema

# multiple_attrs is a list of map attrs that we want to insert
def bulk_create(multiple_attrs) do
  multiple_attrs = Enum.map(multiple_attrs, fn attrs ->
    attrs
    # put timestamps, since they are not auto-generated for us
    |> Map.put(:inserted_at, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
    |> Map.put(:updated_at, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
    # This will pipe the attributes into the anonymous function
    # we will do  embed-specific operations here 
    |> (fn attrs -> 
        # This creates a struct of MyEmbed on the key :my_embed
        %{attrs | my_embed: struct(MyEmbed, attrs.my_embed)}
        # this puts the binary_id on the MyEmbed struct that we created.
        # you will need to ensure that the MyEmbed module implements the Access behaviour
        |> put_in([:my_embed, :id], Ecto.UUID.generate())
    end).()
  end)

  {num, _} = MyApp.Repo.insert_all(MyMainSchema, multiple_attrs)
  {:ok, num}
end

Step by step explanation:

  1. We define this function bulk_create to wrap the Repo.insert_all/2 function.
  2. We alias MyEmbed and MyMainSchema for convenience purposes.
  3. We need to map over the received attributes. In this case, we are receiving a list of maps, hence we need to perform some tasks (like auto-generating timestamps and ids that insert_all does not handle out of the box).
    1. First, we place timestamps. Ecto only accepts timestamps without milliseconds, hence we need to truncate the generated timestamp to only having seconds. Do this for :inserted_at and :updated at.
    2. Second, we need to convert the attributes for the embed into a MyEmbed struct.
    3. Third, we need to generate a UUID for the created struct.

Thereafter, we are then ready to pass it all to MyApp.Repo.insert_all/1.

Hope this helps!