Engineering

14 October, 2021

What you should know about Elixir - Part #1

A periodic blog post, where we share some important and interesting aspects of Elixir and its frameworks. This is the first of many!

Pedro Costa

Software Engineer

What you should know about Elixir - Part #1

As Elixir developers @Coletiv, we periodically come face to face with some unexpected challenges and obstacles in our projects and we believe in sharing our solutions with the community, as we have been doing on some of our blog posts.

However, we feel that there are some interesting Elixir topics, tips, and tricks that do not warrant a whole blog post just by themselves, so we had the idea of starting a periodic blog post, where we share some important and interesting aspects of Elixir and its frameworks.

This is the first of many - we hope you enjoy the content and find it useful!


Quick Tip #1 - Using Repo + Schemas in migrations can land you in trouble

When writing a migration, try to avoid using database functions that depend on schemas, either to fetch or alter information.

If you write a migration that has functions that depend on a schema, and you then alter the schema in the future, the migration you are writing might not be able to run anymore, since it may depend on fields that have been altered or removed, or it will try accessing fields that haven't been added yet.

Below is a more in-depth explanation with examples, in case you're curious. Feel free to skip it if you want.

Example:

  • You have a Users table and schema...

    • On a 1st migration - You want to update some user's names, so you fetch them using the User schema, and then update them.
    • On a 2nd migration - Immediately after writing the 1st migration, you noticed you needed a new column on the Users table, "last_active", so you add it on a 2nd migration. You also update the schema to add this new field, so that it is consistent.
  • You now try to run both migrations at once...

    • And the first one fails! It will say that the "last_active" field, which now exists on the User schema, does not exist in the database yet. This field would only be added to the database by the second migration, therefore locking you from running the 1st migration at all!

Of course, this is only an example, and it is true you could just run the 1st migration independently, and only then include the 2nd one and run it.

However, in real life, this situation could happen, for instance, if your application has some environment that you only deploy to when you have a collection of changes ready and tested, you might still encounter this issue.

Example:

  • You developed the 1st migration, ran it locally, tested it. Then you wrote the 2nd migration, ran it locally, and tested it. Everything works!
  • It is now time to send your code to your staging environment. You commit your code, and your CI tool attempts to run both migrations... and fails - it is attempting to run both migrations, but the committed code already has the updated schema, leading to the error described above on the 1st migration.

Suggestion:

As an alternative, within migrations, we suggest you try to use raw SQL queries as much as you can or using the Repo's insert_all, delete_all, and update_all functions, which also ignore schemas.


Quick Tip #2 - How to use a dependency inside a migration

Sometimes you really need to write some custom logic within a migration, and dependencies may be part of this. However, when running a migration, the application does not automatically start any of its listed dependencies.

To do this, you need to start up the dependency yourself within the migration, using the following code:

Application.ensure_all_started(:dependency_name_atom, :permanent)

Make sure to use ensure_all_started, and not ensure_started. The former makes sure that the dependency also launches any of its own dependencies so that everything runs smoothly.


Quick Tip #3 - Maps and ordering

For practical effects, as a general case, you should assume that maps and any map-based structure in Elixir is unordered.

This implies that you need to be extra careful whenever you convert a list to a map or mapset, since you should never assume the order is kept, even if you previously ordered the list.

In reality, even though this is sound advice, it might be interesting to know that in practice a map's content may actually be shown as in a certain order, but you might want to know that...

  • They are only ordered up to 32 elements - this is due to the internal implementation of maps in Erlang, which is different above and below 32 elements. Below 32 elements, it uses a flatmap, above 32 it uses a hashmap (Hash-Array-Mapped-Trie).
  • The elements of a map are compared by key in ascending term order, and only then by value in key order.

As for the ordering of keys and values, Erlang term ordering rules apply here:

number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string

Here's an example of the iex console display of a map with the same contents being created, and how it automatically shows them with the keys ordered alphabetically:

map-on-iex-console

Note: Keep in mind that the Erlang documents say that the map elements are printed in an arbitrary order. However, in practice, what was written above applies - feel free to try this out yourself in the iex console!

  • You can actually use comparison operators (<, ==, >, ...) between maps. From the Erlang map specifications:

    Maps are ordered by size, two maps with the same size are compared by keys in ascending term order and then by values in key order. In maps, key order integers types are considered less than floats types.


Suggestion:

If there's a problem where you need an ordered structure and you're currently using a map, try to find alternatives, since this is an unsafe practice.

See you next time!

Hope these tips end up being useful to you, as they were to us when they helped us solve some of our past problems. Hope to see you on our next "Elixir - You should know..." article!

Elixir

Join our newsletter

Be part of our community and stay up to date with the latest blog posts.

Subscribe

Join our newsletter

Be part of our community and stay up to date with the latest blog posts.

Subscribe

You might also like...

Go back to blogNext
How to support a list of uploads as input with Absinthe GraphQL

Engineering

26 July, 2022

How to support a list of uploads as input with Absinthe GraphQL

As you might guess, in our day-to-day, we write GraphQL queries and mutations for Phoenix applications using Absinthe to be able to create, read, update and delete records.

Nuno Marinho

Software Engineer

Flutter Navigator 2.0 Made Easy with Auto Router - Coletiv Blog

Engineering

04 January, 2022

Flutter Navigator 2.0 Made Easy with Auto Router

If you are a Flutter developer you might have heard about or even tried the “new” way of navigating with Navigator 2.0, which might be one of the most controversial APIs I have seen.

António Valente

Software Engineer

Enabling PostgreSQL cron jobs on AWS RDS - Coletiv Blog

Engineering

04 November, 2021

Enabling PostgreSQL cron jobs on AWS RDS

A database cron job is a process for scheduling a procedure or command on your database to automate repetitive tasks. By default, cron jobs are disabled on PostgreSQL instances. Here is how you can enable them on Amazon Web Services (AWS) RDS console.

Nuno Marinho

Software Engineer

Go back to blogNext