Idiomatic way to call api based upon param presence

I have two api,
V13.send_push_message(message_uuid)
V13.send_push_message(message_uuid, media_item_uuid)

what is the idiomatic way to call send_push_message/2 if media_item_uuid is not nil?

def send(conn, params = %{
        "message_uuid" => message_uuid
      }) do

        # or idiomatic way to call V13.send_push_message(message_uuid) if media_item_uuid is nil?
        media_item_uuid = Map.get(params, "media_item_uuid", nil)

    V13.send_push_message(message_uuid)
    |> case do
      {:error, _changeset} ->
        put_status(conn, 403)
        |> put_view(ErrorView)
        |> render("403.json", %{message: "something happened."})

      {:ok, push_message_v13} ->
        conn
        |> put_view(PushMessageV13View)
        |> render("addv13.json", %{
          push_message_v13: push_message_v13,
          api_version: "1.3"
        })
    end
  end

make them the same arity and use a guard clause for the nil case.

https://hexdocs.pm/elixir/guards.html

2 Likes

You might even do it without guard.

defmodule V13 do
  def send_push_message(message_uuid, media_item_uuid \\ nil)
  def send_push_message(message_uuid, nil) do
    ...
  end
  def send_push_message(message_uuid, media_item_uuid) do
    ...
  end
end
1 Like
def send_push_message(message_uuid, media_item_uuid \\ nil)

so execution will match to the next function clause that has a do with this \\ nil pattern?
to def send_push_message(message_uuid, nil) do ? (because the second param with match the nil?)

def send_push_message(message_uuid, media_item_uuid \\ nil)

It’s the function signature.

ohh ok, cool. That makes sense, thanks.

Hi! Interesting question.

I agree with @outlog’s suggestion, you can make the functions have the same arity and leverage pattern matching with guards there.

You mentioned wanting to do something if media_item_uuid is not nil. But, that’s a uuid, a binary, right? So you can leverage that and be explicit on your match, using is_binary/1 in a guard clause. Be explicit with the match, always go for the positive case first since it is easier to reason about -> Eg: use is_binary(uuid) instead of not is_nil(uuid). If something you are calling uuid comes back as an integer, you have bigger problems :slight_smile:

# Here are examples of what @outlog mentioned:
def send_push_message(message_uuid, media_item_uuid) when is_binary(media_item_uuid) do
   do_work_when_is_binary( . . . )
end

def send_push_message(message_uuid, nil) do
   do_work_when_is_nil( . . . )
end

def send_push_message(message_uuid, media_item_uuid) do
   raise "Whoa, what is this."
end

In your example, you could also match in the function definition:

def send(conn, params = %{"message_uuid" => message_uuid, "media_item_uuid" => media_item_uuid}) when is_binary(media_item_uuid) do
  message_uuid
  # you can use `media_item_uuid` since it is present, a binary.
  |> V13.send_push_message(message_item_uuid)
  |> _handle_response()
end

def send(conn, params = %{"message_uuid" => message_uuid}) do
  message_uuid
  # you don't use `media_item_uuid` since it is not present.
  |> V13.send_push_message()
  |> _handle_response(conn)
end

def _handle_response({:ok, push_message_v13}, conn) do
  conn
  |> put_view(PushMessageV13View)
  |> render("addv13.json", %{
    push_message_v13: push_message_v13,
    api_version: "1.3"
  })
end

def _handle_response({:error, _changeset}, conn) do
  conn
  |> put_status(403)
  |> put_view(ErrorView)
  |> render("403.json", %{message: "something happened."})
end

Hope this helps @maz!

3 Likes