Table of Contents

1 Elixir   elixir

This is a notebook for all the useful info I can find or read about Elixir.

1.1 Debug in IEx

[2020-03-20 Fri 20:51]

break! is the way to go when you want to set a break point in your code from within IEx:

iex(1)> break!(MyModule.my_func/1)
iex(1)> break!(MyModule, :my_func, 1)

You'll often want to break on a recursive function in which case you can pass an additional argument specifying how many time you want to stop in this function:

break!(MyModule, :my_func, 1, 10)

You can also list all breakpoints:

iex(1)> breaks()
 ID   Module.function/arity   Pending stops
---- ----------------------- ---------------
 1    MyModule.my_func/1        1

Once a break point is set you can go through your code and inspect it:

iex(2)> MyModule.my_func("args")
Break reached: MyModule.my_func/1 (lib/my_module.ex:2)
    1: defmodule MyModule do
    2:   def my_func(args) do
    3:     "#{args}"
    4:   end
pry(1)> args
"args"

When you're done with debugging you can resume the execution by cleaning break points:

pry(2)> respawn

1.2 Inspect failing tests

[2020-03-20 Fri 20:53]

Sometimes it can be handy to inspect our failing test using a debugger. Elixir covers our back by allowing us to use IEx.pry in tests:

require IEx; IEx.pry

You can then run iex -S mix test with the --trace option:

iex -S mix test --trace path/to/test.exs

1.3 Create custom sigils

[2020-03-20 Fri 21:14]

Sigils calls a function to know how to act.

You can define a sigil this way:

defmodule MySigils do
  # returns the downcased string, if option l is given then returns the list of downcased letters
  def sigil_l(string,[]), do: String.downcase(string)
  def sigil_l(string,[?l]), do: String.downcase(string) |> String.graphemes

  # returns the upcased string, if option l is given then returns the list of downcased letters
  def sigil_u(string,[]), do: String.upcase(string)
  def sigil_u(string,[?l]), do: String.upcase(string) |> String.graphemes
end

1.4 Get a value from nested maps

[2020-03-20 Fri 21:16]

The get_in function can be used to retrieve a nested value in nested maps using a list of keys:

nested_map = %{name: %{first_name: "nico"}}            # Example of Nested Map
first_name = get_in(nested_map, [:name, :first_name])  # Retrieving the Key

It will return nil if the key is missing.

1.5 Get all values for a given map key in a list

[2020-03-21 Sat 16:47]

You can also use get_in in conjunction with Access.all/0 to get all the values in a map for a given key:

iex> users = [%{"user" => %{"first_name" => "john", "age" => 23}},
              %{"user" => %{"first_name" => "hari", "age" => 22}},
              %{"user" => %{"first_name" => "mahesh", "age" => 21}}]

iex> get_in(users, [Access.all(), "user", "age"])
[23, 22, 21]

iex> get_in(users, [Access.all(), "user", "first_name"])
["john", "hari", "mahesh"]

You can use the same technique to update all the values for a given key:

iex> user = %{name: "john", books: [%{name: "my soul", type: "tragedy"},
                                    %{name: "my heart", type: "romantic"}, 
                                    %{name: "my enemy", type: "horror"}]}

iex> update_in(user, [:books, Access.all(), :name], &String.upcase/1)
%{books: [%{name: "MY SOUL", type: "tragedy"}, %{name: "MY HEART", type: "romantic"}, %{name: "MY ENEMY", type: "horror"}], name: "john"}

1.6 Arithmetic operators as lambda functions

[2020-03-20 Fri 21:24]

In Elixir every operator is a macro. It makes it possible to use them as them as lambda functions:

iex> Enum.reduce([1,2,3], 0, &+/2)
6
iex> Enum.reduce([1,2,3], 0, &*/2)
0
iex> Enum.reduce([1,2,3], 3, &*/2)
18
iex> Enum.reduce([1,2,3], 3, &-/2)
-1
iex> Enum.reduce([1,2,3], 3, &//2)
0.5

1.7 Pattern match [ vs ] destructure

[2020-03-20 Fri]

= is dedicated to pattern matching but you cannot do [a, b, c] = [1, 2, 3, 4] because the terms count is different so it will raise a MatchError.

iex(11)> [a, b, c] = [1, 2, 3, 4]
(MatchError) no match of right hand side value: [1, 2, 3, 4]

If you want to do that, you have to use destructure/2:

iex(1)> destructure([a, b, c], [1, 2, 3, 4])
[1, 2, 3]
iex(2)> {a, b, c}
{1, 2, 3}
iex> destructure([a, b, c], [1])
iex> {a, b, c}
{1, nil, nil}

1.8 Data Comprehension with filters

[2020-03-21 Sat 16:54]

When using a for loop (a comprehension), you can add conditions to filter values used:

iex> for x <- [1, 2, 3, 4],
         y <- [5, 6, 7, 8],
         rem(x * y, 2) == 0,
         do: {x, y, x * y}

[{1, 5, 5}, {1, 7, 7}, {3, 5, 15}, {3, 7, 21}]

In this example rem(x * y, 2) == 0 is acting as a filter, so tuples that are not matching the condition aren't processed.

1.9 Comprehension with binary strings

[2020-03-21 Sat 16:55]

You can also use comprehension with strings using bitstring notation:

iex> string = "Bounga"
"Bounga"
iex> for << x <- string >>, do: x + 1
'Cpvohb'

1.10 Replace keys in map

[2020-03-21 Sat 22:43]

Sometimes you'll have to modify keys for a provided map. Most of the time that's because you're getting a map out of an API call and you want to change key names for your internal use:

iex> location = %{latitude: 38.8951, longitude: -77.0364}

iex> Enum.into(location, %{}, fn
  {:latitude, lat} -> {:lat, lat}
  {:longitude, long} -> {:long, long}
end)
%{lat: 38.8951, long: -77.0364}

BTW you can also use Enum.into/2 to simply merge things:

Enum.into([1, 2], [])
[1, 2]

iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}

iex> Enum.into(%{a: 1}, %{b: 2})
%{a: 1, b: 2}

iex> Enum.into([a: 1, a: 2], %{})
%{a: 2}

1.11 Group things

[2020-03-21 Sat 23:55]

Let say you have a list of structs or well-defined maps and you want to group them by a given key value, you'll use Enum.group_by/2:

iex> hotel_bookings = [
  %{name: "John", country: "usa", booking_status: :success},
  %{name: "Hari", country: "india", booking_status: :fail},
  %{name: "Mahesh", country: "austria", booking_status: :pending},
  %{name: "Ruchi", country: "paris", booking_status: :success},
  %{name: "Nitesh", country: "malasia", booking_status: :success},
  %{name: "Manoj", country: "japan", booking_status: :fail},
  %{name: "Akhilesh", country: "china", booking_status: :pending},
  %{name: "Rajesh", country: "india", booking_status: :fail},
  %{name: "Payal", country: "london", booking_status: :success},
  %{name: "Kumar", country: "france", booking_status: :fail}
]

iex> Enum.group_by(hotel_bookings, &Map.get(&1, :booking_status))
%{
  fail: [
    %{booking_status: :fail, country: "india", name: "Hari"},
    %{booking_status: :fail, country: "japan", name: "Manoj"},
    %{booking_status: :fail, country: "india", name: "Rajesh"},
    %{booking_status: :fail, country: "france", name: "Kumar"}
  ],
  pending: [
    %{booking_status: :pending, country: "austria", name: "Mahesh"},
    %{booking_status: :pending, country: "china", name: "Akhilesh"}
  ],
  success: [
    %{booking_status: :success, country: "usa", name: "John"},
    %{booking_status: :success, country: "paris", name: "Ruchi"},
    %{booking_status: :success, country: "malasia", name: "Nitesh"},
    %{booking_status: :success, country: "london", name: "Payal"}
  ]
}

You can also go further and get a list one of a key value only (here we'll extract the names) in groups rather than the whole map:

iex>  Enum.group_by(hotel_bookings, &Map.get(&1, :booking_status), &Map.get(&1, :name))
%{
  fail: ["Hari", "Manoj", "Rajesh", "Kumar"],
  pending: ["Mahesh", "Akhilesh"],
  success: ["John", "Ruchi", "Nitesh", "Payal"]
}

1.12 Find N slowest tests

[2020-03-22 Sun 00:06]

It can be useful to find out what are your slowest tests in your test suite so you can act accordingly, Mix helps with that:

mix test --slowest 3

will show us the three slowest tests.

1.13 Custom inspection for structure

[2020-03-22 Sun 00:14]

When you're examining your code, you'll often find yourself using IO.inspect to get info about your structure.

In Elixir, when you define a structure you can also declare its implementation for the Inspect module:

defmodule Student do  
  defstruct name: "John", place: "Earth"
end

defimpl Inspect, for: Student do
  def inspect(student, _opts) do
    """
    -----------|---------------------
       Name    :     #{student.name} 
    -----------|---------------------
       Place   :     #{student.place}
    -----------|---------------------
    """
  end
end

iex> %Student{}
-----------|---------------------
   Name    :     John 
-----------|---------------------
   Place   :     Earth
-----------|---------------------

1.14 Querying Ecto for a subset of the fields along with relation info   ecto

When querying your database you often don't need to retrieve all the data for each row, you use-case may only need a subset of it.

Ecto provides a clean way to do that even if you need to also retrieve relation computed info along in a single query:

query =
  from u in "users",
  join: c in "comments",
  on: c.user_id == u.id,
  where: u.active == true,
  group_by: [u.name, u.email],
  select: %{name: u.name, email: u.email, comments_count: count(c.id)}

Repo.all(query)

# => [
    %{comments_count: 23, email: "gandalf@greypilgrim.com", name: "Gandalf"},
    %{comments_count: 45, email: "aragorn@rangers.com", name: "Aragorn"},
    %{comments_count: 566, email: "sonofgloin@lonelymountain.com", name: "Gimli"},
    %{...},
    ...
  ]

Author: Nicolas Cavigneaux

Created: 2020-03-22 Sun 11:53

Validate