What if I move |> Repo.insert(), outside and below the function and the Enum.each in the following code?

I’m reading this nice book series, Phoenix inside out. At one point database is seeded from a CSV file.
Following is the code at page 131-132 (seeds.exs)

alias NimbleCSV.RFC4180, as: CSV
alias Mango.Catalog.Product
alias Mango.Repo

"priv/seed_data/product_list.csv"
|> File.read()
|> CSV.parse_string()
|> Enum.each(fn [_, name, price, sku, is_seasonal, image, pack_size, category] ->
  is_seasonal = String.to_existing_atom(is_seasonal)
  price = Decimal.new(price)

  %Product{
    name: name,
    price: price,
    sku: sku,
    is_seasonal: is_seasonal,
    image: image,
    pack_size: pack_size,
    category: category
  }
  |> Repo.insert()
end)

From this code I for the first time learnt that functions passed to the Enum.each can have multiple steps separated by new lines.
After -> there are actually 4 steps starting from is_seasonal, price, %Product{ and |> Repo respectively.
My questions are,

  1. Can these steps be separated by something other than just a new line, like some comma or something else, because (coming from imperative languages) that will look more readable.
  2. Can I move |> Repo.insert() below the end) in the last line?

(Note: the second question is more important)

Thank You for reading my post!

Can these steps be separated by something other than just a new line, like some comma or something else, because (coming from imperative languages) that will look more readable.

You can separate them with ;:

# ...
|> Enum.each(fn [_, name, price, sku, is_seasonal, image, pack_size, category] ->
  is_seasonal = String.to_existing_atom(is_seasonal); price = Decimal.new(price)
# ...

Can I move |> Repo.insert() below the end) in the last line?

Then you would need to use Enum.map instead of Enum.each with something like Repo.insert_all/3:

data = 
  "priv/seed_data/product_list.csv"
  |> File.read()
  |> CSV.parse_string()
  |> Enum.map(fn [_, name, price, sku, is_seasonal, image, pack_size, category] ->
    is_seasonal = String.to_existing_atom(is_seasonal)
    price = Decimal.new(price)

    %{
      name: name,
      price: price,
      sku: sku,
      is_seasonal: is_seasonal,
      image: image,
      pack_size: pack_size,
      category: category
    }
  end)

Repo.insert_all("products", data)
2 Likes

Thank You for the quick reply!

Now after your answer I think that moving it outside and making it insert_all may make it more efficient like converting N+1 database query into a multiple query. :slight_smile:

1 Like

Thank You so much for the quick and detailed reply!

1 Like

To answer your first question, you can also extract the anonymous function to a named private function as well.

So, something like this (untested):

"priv/seed_data/product_list.csv"
|> File.read()
|> CSV.parse_string()
|> Enum.each(&insert_product_from_row/1)

defp insert_product_from_row([_, name, price, sku, is_seasonal, image, pack_size, category]) do
  is_seasonal = String.to_existing_atom(is_seasonal)
  price = Decimal.new(price)

  %Product{
    name: name,
    price: price,
    sku: sku,
    is_seasonal: is_seasonal,
    image: image,
    pack_size: pack_size,
    category: category
  }
  |> Repo.insert()
end
3 Likes