Skip to content
/ useful Public

πŸ‡¨πŸ‡­ A collection of useful functions for working in Elixir

License

Notifications You must be signed in to change notification settings

dwyl/useful

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

useful

A collection of useful functions for building Elixir Apps.

GitHub Workflow Status codecov.io Hex.pm Libraries.io dependency status contributions welcome HitCount

swiss-army-knife

Why? 🀷

We found ourselves copy-pasting a few useful "helper" functions across our Elixir projects ...
it wasn't "DRY", so we created this library.

What? πŸ’­

A library of useful functions that we reach for when building Elixir Apps.

Who? πŸ‘€

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.

How? πŸ’»

Install ⬇️

Install by adding useful to your list of dependencies in mix.exs:

def deps do
  [
    {:useful, "~> 1.14.0"}
  ]
end

Function Reference

atomize_map_keys/1

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_map/1

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_in_default/1

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.

list_tuple_to_unique_keys/1

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_item_from_list/2

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_map/1

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_tuple/1

Stringify a tuple of any length; useful in debugging.

iex> tuple = {:ok, :example}
iex> Useful.stringify_tuple(tuple)
"ok: example"

typeof/1

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"

empty_dir_contents/1

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}

Docs πŸ“œ

Detailed docs available at: https://hexdocs.pm/useful/Useful.html


Help Us Help You! πŸ™

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!