We found ourselves copy-pasting a few useful "helper" functions
across our Elixir projects ...
it wasn't
"DRY",
so we created this library.
A library of useful functions
that we reach for
when building Elixir
Apps.
This library is used in our various Elixir
/ Phoenix
apps.
As with everything we do it's Open Source, Tested and Documented
so that anyone can benefit from it.
Install by adding useful
to your list of dependencies in mix.exs
:
def deps do
[
{:useful, "~> 1.14.0"}
]
end
Converts a Map
that has strings as keys (or mixed keys)
to have only atom keys. e.g:
#Β map that has different types of keys:
my_map = %{"name" => "Alex", id: 1}
Useful.atomize_map_keys(my_map)
%{name: Alex, id: 1}
Works recursively for deeply nested maps:
person = %{"name" => "Alex", id: 1, details: %{"age" => 17, height: 185}}
Useful.atomize_map_keys(person)
%{name: Alex, id: 1, details: %{age: 17, height: 185}}
Flatten a Map
of any depth/nesting:
iex> map = %{name: "alex", data: %{age: 17, height: 185}}
iex> Useful.flatten_map(map)
%{data__age: 17, data__height: 185, name: "alex"}
Note: flatten_map/1
converts all Map keys to Atom
as it's easier to work with atoms as keys
e.g: map.person__name
instead of map["person__name"]
.
We use the __
(double underscore)
as the delimiter for the keys of nested maps,
because if we attempt to use .
(period character)
we get an error:
iex(1)> :a.b
** (UndefinedFunctionError) function :a.b/0 is undefined (module :a is not available)
:a.b()
Get a deeply nested value from a map.
get_in_default/3
Proxies Kernel.get_in/2
but allows setting a default
value as the 3rd argument.
iex> map = %{name: "alex", detail: %{age: 17, height: 185}}
iex> Useful.get_in_default(map, [:data, :age])
17
iex> Useful.get_in_default(map, [:data, :everything], "Awesome")
"Awesome"
iex> Useful.get_in_default(conn, [:assigns, :person, :id], 0)
0
We needed this for getting conn.assigns.person.id
in our App
without having to write a bunch of boilerplate!
e.g:
person_id =
case Map.has_key?(conn.assigns, :person) do
false -> 0
true -> Map.get(conn.assigns.person, :id)
end
is just:
person_id = Useful.get_in_default(conn, [:assigns, :person, :id], 0)
Muuuuuuch cleaner/clearer! π
If any of the keys in the list is not found
it doesn't explode with errors,
simply returns the default
value 0
and continues!
Note: Code inspired by: stackoverflow.com/questions/48781427/optional-default-value-for-get-in
All credit to@PatNowak
π
The ideal syntax for this would be:
person_id = conn.assigns.person.id || 0
But Elixir
"Me no likey" ...
So this is what we have.
Turns a list of tuples with the same key into a list of tuples with unique keys. Useful when dealing with "multipart" forms that upload multiple files. e.g:
parts = [
{"files",[{"content-type", "image/png"},{"content-disposition","form-data; name=\"files\"; filename=\"first.png\""}],%Plug.Upload{path: "..", content_type: "image/png",filename: "first.png"}},
{"files",[{"content-type", "image/webp"},{"content-disposition","form-data; name=\"files\"; filename=\"second.webp\""}],%Plug.Upload{path: "...",content_type: "image/webp",filename: "second.webp"}}
]
Useful.list_tuples_to_unique_keys(parts) =
[
{"files-1",[{"content-type", "image/png"},{"content-disposition","form-data; name=\"files\"; filename=\"first.png\""}],%Plug.Upload{path: "..", content_type: "image/png",filename: "first.png"}},
{"files-2",[{"content-type", "image/webp"},{"content-disposition","form-data; name=\"files\"; filename=\"second.webp\""}],%Plug.Upload{path: "...",content_type: "image/webp",filename: "second.webp"}}
]
Remove an item
from a list
.
With numbers:
list = [1, 2, 3, 4]
Useful.remove_item_from_list(list, 3)
[1, 2, 4]
With a List
of Strings
:
list = ["climate", "change", "is", "not", "real"]
Useful.remove_item_from_list(list, "not")
["climate", "change", "is", "real"]
The list
is the first argument to the function
so it's easy to pipe:
get_list_of_items(person_id)
|> Useful.remove_item_from_list("item_to_be_removed")
|> etc.
Stringify a Map
e.g. to store it in a DB or log it stdout.
map = %{name: "alex", data: %{age: 17, height: 185}}
Useful.stringify_map(map)
"data__age: 17, data__height: 185, name: alex"
Stringify a tuple of any length; useful in debugging.
iex> tuple = {:ok, :example}
iex> Useful.stringify_tuple(tuple)
"ok: example"
Returns the type of a variable, e.g: "function" or "integer"
Inspired by
typeof
from JavaScript
land.
iex> myvar = 42
iex> Useful.typeof(myvar)
"integer"
Empties the directory (deletes all files and any nested directories) recursively, but does not delete the actual directory. This is useful when you want to reset a directory, e.g. when testing.
iex> dir = "tmp" # contains lots of sub directories and files
iex> Useful.empty_dir_contents(dir)
{:ok, dir}
Detailed docs available at: https://hexdocs.pm/useful/Useful.html
If you need a specific helper function or utility (e.g: something you found useful in a different programming language), please open an issue so that we can all benefit from useful functions.
Thanks!