Creation of a parent with many children with one single Absinthe request

Hi everyone,

I’m new to Elixir/Phoenix and I am training on pushing data into the database, with GraphQL requests & Absinthe.

I have the following data model :

  • Balance Model: has a name and many Balance Criterion children
  • Balance Criterion: has a name and is linked to a parent Balance Model

My training goal is to push with one GraphQL mutation a Balance Model with related criteria into the database:

mutation {
  createModelWithCriteria(
    name: "new model 21",
    criteria: [{name: "XX"},{name: "YY"}]
  ) {
	name
	criteria {
		name
	}
  }
}

I got inspired by the Absinthe mutations doc : Complex Arguments — absinthe v1.7.0
Here is the schemas I have built:

schema "balance_model" do
      field :name, :string
      has_many :balance_criterion, AppyogaPhoenix.BalanceModels.BalanceCriterion
      timestamps()
   end

   def changeset(balance_model, attrs) do
      balance_model
      |> cast(attrs, [:name])
      |> validate_required([:name])
      |> validate_length(:name, min: 1, max: 40)
   end
schema "balance_criterion" do
      field :name, :string
      belongs_to :balance_model, AppyogaPhoenix.BalanceModels.BalanceModel
      timestamps()
   end

   def changeset(balance_criterion, attrs) do
      balance_criterion
      |> cast(attrs, [:name])
      |> validate_required([:name])
      |> validate_length(:name, min: 2, max: 40)
   end

And here is the module logic:

def create_criterion(balance_model, attrs \\ %{}) do
      balance_model
      |> Ecto.build_assoc(:balance_criterion)
      |> BalanceCriterion.changeset(attrs)
      |> Repo.insert()
   end

   def create_model(attrs \\ %{}) do
      %BalanceModel{}
      |> BalanceModel.changeset(attrs)
      |> Repo.insert()
   end

   def create_criterion_list(balance_model, []), do: []
   def create_criterion_list(balance_model, [h | t]) do
      [{:ok, criterion} = create_criterion(balance_model,h) | create_criterion_list(balance_model, t)]
   end

   def create_model_with_criteria(attrs \\ %{}) do

      {criterion_list, model_attrs} = Map.pop(attrs, :criteria)

      Repo.transaction fn ->
         with {:ok, balance_model} <- create_model(model_attrs),
              criterion_list <- Enum.map(create_criterion_list(balance_model, criterion_list), fn {:ok, criterion} -> criterion end) do
           %{balance_model | balance_criterion: criterion_list}
         end
      end

   end

I call create_model_with_criteria with an Absinthe resolver and attrs is :

%{criteria: [%{name: "XX"},%{name: "YY"}], name: "model 21"}

It does work fine.
I have two questions :

  1. I find my code maybe a bit heavy when it comes to “create_criterion_list” (the recursive thing, the Enum on it…). Is there a way to make simpler the creation of a parent with a list of children ?
  2. If i do not respect a database constraint (name unicity, min chars for criterion name…) nothing is inserted, which is fine. But what I receive as a response to my API call is an Exception, while I was waiting for an Ecto error. Is that possible with a Repo.transaction or do I have to switch to a Ecto.Multi to be able to catch properly the error when there is one ?

Thank you for the advices you could give me.

Benoît