Basic but useful example of Elixir Protocols

August 11, 2019

Assuming you know the theory, in this example we’ll provide one login interface for different type of users.

Let’s start a new project:

mix new protdemo --sup


This example shows that passing different data types to our login function will trigger a different implementation.

We’ll have one struct per user role.

Create a new admin directory under lib/protdemo and put there a new file admin.ex:

# lib/protdemo/admin/admin.ex

defmodule Protdemo.Admin do
  defstruct [:admin_name, :password]

  def login(who), do: {:ok, who.admin_name <> " logged in as Admin"}

Create another directory called user under lib/protdemo and put there a new file user.ex:

# lib/protdemo/user/user.ex

defmodule Protdemo.User do
  defstruct [:user_name, :password]

  def login(who), do: {:ok, who.user_name <> " logged in as User"}


The most important part now. Create a new file auth.ex under lib/protdemo

# lib/protdemo/auth.ex

defprotocol Protdemo.Auth do
  def login(creds)

defimpl Protdemo.Auth, for: Protdemo.Admin do
  def login(admin_creds), do: Protdemo.Admin.login(admin_creds)

defimpl Protdemo.Auth, for: Protdemo.User do
  def login(user_creds), do: Protdemo.User.login(user_creds)

Having different functions called based on the data type, we can implement completely different logic for admin and regular users.

The joining point is one function we’ll put to lib/protdemo.ex:

# lib/protdemo.ex

defmodule Protdemo do
  alias Protdemo.Auth

  def login(creds), do: Auth.login(creds)


# test/protdemo_test.exs

defmodule ProtdemoTest do
  use ExUnit.Case
  doctest Protdemo

  alias Protdemo.Admin
  alias Protdemo.Auth
  alias Protdemo.User

  test "Log in as User" do
    {:ok, "rafal logged in as User"} = Auth.login(%User{user_name: "rafal", password: "test"})

  test "Log in as Admin" do
    {:ok, "rafal logged in as Admin"} = Auth.login(%Admin{admin_name: "rafal", password: "test"})

I believe simple code examples are worth more than 1000 words.