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 :
- 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 ?
- 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