Handle optional request params

Hello,

I need to process optional params (from post body) to send them in request, my actual implementation :

body =
  if params["book_name"] do
    %{
      book_price: book_price,
      book_name: params["book_name"]
    }
  else
    %{
      book_price: book_price
    }
  end

with {:ok, result} <- post_book(body, opts) do
  {:ok, result}
else
  {:error, error} -> {:error, error}
end

is there more elegant way to do it ?

Here you go:

defmodule Example do
  # function specification
  @spec sample(params :: map(), body :: map(), optional_params :: list(atom())) :: map()
  # optional param values, so you can call
  # Example.sample(params)
  def sample(params, body \\ %{}, optional_params \\ [])
      # function guards
      when is_map(params) and is_map(body) and is_list(optional_params) do
    # list of atoms
    optional_params
    # map with atom keys and string values
    |> Map.new(&{&1, Atom.to_string(&1)})
    # reduce optional params over base/generic body
    |> Enum.reduce(body, fn
      # when params have optional params (string key)
      {atom_param, string_param}, acc when is_map_key(params, string_param) ->
        # put to accumulator (body) value from params (string key) under atom key
        Map.put(acc, atom_param, params[string_param])

      # in any other case i.e. when said optional param does not exists in params 
      _optional_param, acc ->
        # simply return our accumulator
        acc
    end)
  end
end

# some data
book_price = 10
base_body = %{book_price: book_price}
optional_params = ~w[book_name]a

# empty params
body = Example.sample(%{}, base_body, optional_params)

# non empty params
body = Example.sample(%{"book_name" => "Sample"}, base_body, optional_params)

Also in this part:

with {:ok, result} <- post_book(body, opts) do
  {:ok, result}
else
  {:error, error} -> {:error, error}
end

with is useless, because you always return post_body/2 output anyway.

Helpful resources:

  1. Atom.to_string/1
  2. Enum.reduce/3
  3. Kernel.is_map/1
  4. Kernel.is_map_key/2
  5. Kernel.is_list/1
  6. Map.new/2
  7. Map.put/3
  8. Pages |> Patterns and Guards @ Elixir documentation
  9. Words list sigil (w)
1 Like

Hi @Eiji , thank you for your answer, I will give it a try !

I didn"t understand this expression : ~w[book_name]a

@xgeek116 That’s why I’m always adding Helpful resouces section. :wink:

Hint: sigil calls starts with ~ character. :smiling_imp:

1 Like

Here’s a minimal approach using pattern matching in a private function head to conditionally add an attribute.

defp maybe_add_book_name(body, %{“book_name” => book_name}), do: Map.put(body, :book_name, book_name)
defp maybe_add_book_name(body, _params), do: body

body = %{book_price: book_price} |> maybe_add_book_name(params)
1 Like