Variable into keyword list's key?

Hi, I have a piece of code like this:

Repo.update_all(query, inc: [option1: 1])

I want to pass a variable as a key to that keyword list. So I get for example 1, 2 or 3 in params, and want to turn that into keys option1 / option2 / option3 and use like this:

Repo.update_all(query, inc: [option1: 1])
Repo.update_all(query, inc: [option2: 1])
Repo.update_all(query, inc: [option3: 1])

Can’t figure out the proper syntax for that. Thank you very much!

Keyword list syntax like you have is just sugar around a list of tuples, so you can always fallback to the ordinary tuple syntax:

param = :option1
Repo.update_all(query, inc: [{param, 1}])

ah that’s right! thank you!

and please forgive a noob question but how do I actually turn the param value of “1” or “2” into atoms :option1 and :option2 ?

You mean like "option#{param}" |> String.to_existing_atom()? There is also String.to_atom/1 as well, but you really shouldn’t use that one… It will make your application attackable if params are user input. Any user then could make your application die because OOM by randomly feeding unique params into it. Always remember, you can only create about 1M atoms per default.

An even better version is some kind of whitelisting, which selectively converts strings to atoms, roughly like this:

def convert("param_1"), do: :param_1
def convert("param_2"), do: :param_2

Of course, this can be done during compiletime pretty well and then String.to_atom/1 is not that much a risk anymore:

@params [1, 2, 3, 4]
@param_map for i <- @params, do: {"param_#{i}", String.to_atom("param_#{i}")} # alternatively you can also use :"param_#{i}" (quoted atoms can do interpolation)

Enum.each(@param_map, fn {str, atom} ->
  def convert(unquote(str)), do: unquote(atom)
end)

Of course you can populate @param_map manually as well or combine generation and manual:

@param_map [{1, :foo}] ++ for i <- …
3 Likes

Personally I’d go with a simple case expression or function head:

param = "1"
Repo.update_all(query, inc: [{param_to_column(param), 1}])
#elsewhere
defp param_to_column("1"), do: :foo
2 Likes