Я использую with
только в определенных c ситуациях, и в них это действительно помогает, в основном, когда это некоторый набор операций, напоминающих «трубу», например, вам нужны результаты из предыдущих шагов в следующий, но они действительно разнородны, и у вас нет или не имеет смысла создавать какую-либо структуру токенов для хранения преобразований и ошибок (аналогично набору изменений ecto).
В тех случаях, если вы знаете необходим шаг с ошибкой. Я считаю, что упаковка операторов with
в помеченный кортеж помогает, так как тогда вы можете сопоставить указанный тег c с ошибкой. Кроме того, вы на самом деле не используете код idiomati c, потому что вы используете with
просто как выражение присваивания, если вы используете сопоставление с образцом, он становится более читабельным и, на мой взгляд, более идиоматическим c. В вашем примере это будет означать:
with {_, %Model1{} = m1} <- {Model1, Repo.get(Model1, params["model1_id"])},
{_, %Model2{} = m2} <- {Model2, Repo.get(Model2, params["model2_id"])}
do
# we have both m1 and m2 and they are respectively instances of Model1 and Model2
# do something with them
{:ok, {m1, m2}}
else
{Model1, _} ->
#failed fetching Model1
{:error, :no_model1}
{Model2, _} ->
#failed fetching Model2
{:error, :no_model2}
end
Соответствие шаблонов именно той структуре, которую вы хотите, и зная, что Repo.get вернет либо структуру схемы, либо nil, делает это так, что вам не нужно проверьте, является ли он nil, если это не структура схемы, он будет равен nil (если вы не используете Repo.get с предложением select, где вы возвращаете что-то еще из c).
Помните, что доступ к params["some_key"]
может вернуть nil
, и это вызовет исключение при попытке выполнить Repo.get
, поэтому вы можете добавить еще два оператора с условиями для идентификатора, а также позволить вам вернуть идентификатор в случае, если они не были найдены, предполагая что идентификаторы являются числовыми идентификаторами (если двоичное изменение is_integer
на is_binary
):
with {_, id1} when is_integer(id1) <- {:id1, Map.get(params, "model1_id")},
{_, id2} when is_integer(id2) <- {:id2, Map.get(params, "model2_id")},
{_, _, %Model1{} = m1} <- {Model1, id1, Repo.get(Model1, id1)},
{_, _, %Model2{} = m2} <- {Model2, id2, Repo.get(Model2, id2)}
do
# we have both m1 and m2 and they are respectively instances of Model1 and Model2
# do something with them
{:ok, {m1, m2}}
else
{id_type, id_value} when id_type in [:id1, :id2] ->
# one of the id params wasn't an integer
{:error, {:unexpected_id, id_type, id_value}}
{Model1, id, _} ->
# failed fetching Model1
{:error, {:no_model1, id}}
{Model2, id, _} ->
# failed fetching Model2
{:error, {:no_model2, id}}
end
Вероятно, есть лучшая обработка параметров, которые должны быть выполнены, например, их проверка перед достижением этого этапа выполнения , и если это будет сделано, вы, вероятно, могли бы go с оператором case
, поскольку это всего 2 «случая»:
case Repo.get(Model1, valid_id_1) do
%Model1{} = model1 ->
case Repo.get(Model2, valid_id_2) do
%Model2{} = model2 -> {:ok, {model1, model2}}
nil -> {:error, {:no_model2, valid_id_2}}
end
nil -> {:error, {:no_model1, valid_id_1}}
end