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"}
]
endConverts 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)
0We 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)
endis 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 || 0But 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"truncate; To shorten (something) by, or as if by, cutting part of it off. wiktionary.org/wiki/truncate
Returns a truncated version of the String according to the desired length.
Useful if your displaying an uncertain amount of text in an interface.
E.g. the "bio" field on GitHub can be up 160 characters.
Most people don't have a bio but some use every character.
If you're displaying profiles in an interface, you want a predictable length.
Usage:
iex> input = "You cannot lose what you never had."
iex> Useful.truncate(input, 18)
"You cannot lose ..."The optional third argument terminator
allows specify any String or an empty String if you prefer
as the terminator for your truncated text:
iex> input = "do or do not there is no try"
iex> Useful.truncate(input, 12, "") # no ellipsis
"do or do not"
iex> input = "It was the best of times, it was the worst of times"
iex> Useful.trucate(input, 25, "")
"It was the best of times"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!
