Iterate through multiple maps parallely?

I have a CSV file uploaded, parsed and inserted into DB, all is fine,
data in the CSV row is:

name, father_mobile, mother_mobile

I want to insert name into students table, while father_mobile, mother_mobile into parents table as separate records, as each student has two foreign keys, father_id & mother_id

The relation is student has_one mother, has_one father, through father_id & mother_id , so, I will created three records from one CSV row, student& two parents.

So far, I have achieved this:

// collect fathers:
father_rows= Enum.map(ecto_rows, fn %{father_mobile: father_mobile} -> %{parent_type: "father", mobile_number: father_mobile} end)
// collect mothers: 
mother_rows= Enum.map(ecto_rows, fn %{mother_mobile: mother_mobile} -> %{parent_type: "mother", mobile_number: mother_mobile} end) 
// drop from students:
students_rows= Enum.map(ecto_rows, fn row -> Map.pop(row, "father_mobile") Map.pop(row, "mother_mobile")  end)

Now, I have three maps ready to be inserted into DB, but how would I achieve the association?

I have tried Repo.insert_all successfully for students, but, how would I associated fathers and mothers?
I thought of iterating on maps one by one, but how would I do so?

What’s your thoughts about it?

1 Like

I think it is a mistake to remove the fathers and mothers from your original data structure here, as you want to use them together immediately afterwards.

Rather, you should loop over the CSV, and for every row insert them:

for student_info <- csv_data do
  Repo.transaction(fn -> 
    {:ok, father} = insert_father(student_info)
    {:ok, mother} = insert_mother(student_info)
    {:ok, student} = insert_student(student_info, father, mother)
  end)
end

def insert_father(%{father_mobile: mobile}) do
  Repo.insert(%Parent{parent_type: "father", mobile_number: mobile}
end

def insert_mother(%{mother_mobile: mobile}) do
  Repo.insert(%Parent{parent_type: "mother", mobile_number: mobile})
end

def insert_student(student_info = %{foo: 1, bar: 2}, father, mother) do
  %Student{}
  |> Ecto.Changeset.cast(student_info, [:foo, :bar])
  |> Ecto.Changeset.cast(%{father_id: father.id, mother_id: mother.id}, [:father_id, :mother_id])
  Repo.insert()
end

There might be even better ways; I’m not an Ecto expert (yet! :stuck_out_tongue_winking_eye:) .

Thanks Qqwy for help, but I don’t understand how function insert_student works above, what would cast do? can you please explain line by line?

What it does, is take a new (all fields having their default values in the Student schema) %Student{}.

Then the cast (see Ecto.Changeset.cast’s documentation) fills in each field listed in the third argument from the second argument in the first argument. In this case, it will fill in the :foo and :bar fields in the Student, as long as student_info contains these (which it should, as we matched on it in the function head).

Then the next cast will do the same with the map containing the references to the father and mother structures.

Finally, this changeset is inserted in the repository.