I’m working on a game app that has an answers table and a categories table. This relationship is many to many.
The admins for the app can add answers and categories individually using usual controller format. However, I want to create a way to bulk associate answers to categories. On a category page there is a textarea to receive a comma separated list of strings. Upon hitting submit, I want to achieve the following steps,
- assess if any of the strings already exist as an answer
- for any that do not exist, create an record in the answers table
- for all the answers that were given, create the association in the join table
Here is my attempt.
def put_answers_to_category(category, answers_string) do
answers =
answers_string
|> parse_answers_string()
|> find_existing_and_create_new()
old_category =
category
|> Repo.preload(:answers)
|> IO.inspect()
category
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:answers, Enum.uniq(answers ++ old_category.answers))
|> IO.inspect()
|> Repo.update()
end
def parse_answers_string(answers_string) do
answers_string
|> String.split(",", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
end
def find_existing_and_create_new(answers_list) do
existing_answers =
Answer
|> where([a], a.raw in ^answers_list)
|> Repo.all()
existing_answers_raw = Enum.map(existing_answers, & &1.raw)
new_answers =
answers_list
|> Enum.filter(fn ans -> !Enum.member?(existing_answers_raw, ans) end)
|> Enum.map(fn ans -> %{raw: ans} end)
|> Enum.map(&create_answer/1)
|> Enum.map(fn {:ok, answer} -> answer end)
existing_answers ++ new_answers
end
This seems to be working but I think it may be verbose/inefficient. I’ve read the docs around cast_assoc
and put_assoc
but I couldn’t quite find a better way. Is there a better/more efficient way to do this?