Rewriting elixirstream.com from Rails to Phoenix

Dependency injection The title seems a little bit strange at first but just hang on here for a second, I will explain it bit by bit. We launched http://elixirstream.com with existing code base from http://emberflare.com to have a blog post and article sharing platform for fellow Alchemists. The code base itself was written in Rails and we wanted to launch it as quickly as possible bearing in mind that we will rewrite it to Phoenix and Elixir as soon as we can. And what you know, it happened! The purpose of this blog post is to share my findings in regards to Phoenix as a framework from a technical standpoint and give a brief overview of user authentication process, which covers most of the necessary parts of the web development and will allow you to start building "real world" apps. I want to warn you that is is my first Phoenix app so some of my understanding about the conventions might be wrong. Anyways, the critic is always welcome. :)

Registration process

The first bit of the application I wanted to tackle was the registration and logging in of the user.In the previous Rails app we used has_secure_password that is built in in Rails to hash our passwords. Under the good it uses the Bcrypt encryption algorithm that generates different hashes each time you do an encryption, yet allowing to compare them for equality. For Elixir side of things, I found an amazing library to do password encryption and compare the encrypted password to it's unencrypted counterpart: http://hexdocs.pm/comeonin/. I checked it against my own encrypted password and it worked like a charm. Let's take a look at the users model:

defmodule ElixirStream.User do  
  use ElixirStream.Web, :model
  use Ecto.Model.Callbacks
  alias ElixirStream.Repo

  before_insert :set_password_digest

  schema "users" do
    field :username, :string
    field :password_digest, :string
    field :email, :string
    field :password, :string, virtual: true
    has_many :entries, Entry
    timestamps
  end

  @required_fields ~w(username email password)
  @optional_fields ~w()

  @doc """
  Creates a changeset based on the `model` and `params`.

  If `params` are nil, an invalid chageset is returned
  with no validation performed.
  """

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
    |> validate_length(:username, min: 5)
    |> validate_length(:username, max: 50)
    |> validate_length(:password, min: 8)
    |> validate_length(:password, max: 100)
    |> validate_unique(:email, on: Repo)
    |> validate_unique(:username, on: Repo)
  end


  def set_password_digest(changeset) do
    password = Ecto.Changeset.get_field(changeset, :password)
    change(changeset, %{password_digest: crypt_password(password)})
  end

  def crypt_password(password) do
    Comeonin.Bcrypt.hashpwsalt(password)
  end
end  

The purpose of the ecto model is to define validations and to transport the data. I really enjoy the concept of changesets. Once you call the following code:

model  
    |> cast(params, @required_fields, @optional_fields)

the changeset is being created with the name of our model as a first argument and the parameters for a given model as a second argument. Both required and optional parameters are being casted to corresponding model columns, or more precisely, values on our %User{} struct. After that is done, we simply pipe the changeset to a chain of validation functions. One thing to note here, that ecto can also validated the data against our repository (database) like so:

    |> validate_unique(:email, on: Repo)

After the data has gone trough the validations, the changeset is being returned to us. It is either valid or invalid.
The thing to note here is that User does not store the actual password in the database, that's why we had to use a virtual attribute on the model to achieve that. The line field :password, :string, virtual: true allows us to set this attribute, yet not store it in the database on the insert or update.
The line use Ecto.Model.Callbacks allows us to define callback functionality to our models. We set the password digest at before_insert :set_password_digest. This made me cringe at first, and if you check commit history, you can see that my attempt was to avoid callbacks at any cost. The reason behind my cringing is simple - I have been bitten by ActiveRecord callbacks multiple times, but this is different, since this is the data that has to be there at any insert and after a brief discussion with my colleague, we agreed to use callbacks password hashing.
In the function set_password_digest we see that we get the password field of the changeset and then update our password_digest with it's encrypted counterpart:

  def set_password_digest(changeset) do
    password = Ecto.Changeset.get_field(changeset, :password)
    change(changeset, %{password_digest: crypt_password(password)})
  end

After this function is done with its task, we have a changeset that we can store.

I want to abstract from the framework as much as possible, so I created a RegisterAction in /lib folder.

defmodule ElixirStream.RegisterAction do  
  alias ElixirStream.User
  def sign_up(params) do
    changeset = User.changeset(%User{}, params)
    if changeset.valid? do
      user = ElixirStream.Repo.insert(changeset)
      {:ok, user}
    else
      {:error, changeset}
    end
  end
end  

As you can see, it just casts the parameters to a user struct and performs validations on it. If is valid, then we return {:ok, user} tuple and if the changeset is invalid, we return an {:error, changeset} tuple. In the first case, we return the user to have the ability to add user.id to set a session in the controller. In the second case, we need to return a changeset to show error messages on the form. The code in controller looks like following:

  def register(conn, %{"user" => user_params}) do
    case ElixirStream.RegisterAction.sign_up(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "You have signed up and logged in!")
        |> put_session(:user_id, user.id)
        |> redirect(to: entry_path(conn, :index))
      {:error, changeset} ->
        conn
        |> render("register_form.html", changeset: changeset)
    end
  end

Please note the line put_session(:user_id, user.id) where we put a user.id in a session. In Phoenix controller actions, you always have a connection or conn for short. This is where the all connection data is stored, including sessions, assigns (We will talk about that later), headers e.t.c.

So, after this we have a session set and how do we provide a user's context to the application? After a brief research, I decided to go with writing a custom plug and adding current user to conn assigns. Let's see example:

defmodule ElixirStream.Plugs.CheckAuthentication do  
  import Plug.Conn
  import Plug.Session

  def init(options) do
    options
  end

  def call(conn, _) do
    user_id = get_session(conn, :user_id)
    if session_present?(user_id) do
      assign(conn, :current_user, ElixirStream.Repo.get(ElixirStream.User, user_id))
    else
      conn
    end
  end

  def session_present?(user_id) do
    case user_id do
      nil -> false
      _   -> true
    end
  end
end  


First of all we import Plug.Conn and Plug.Session to import all functions that can work with sessions and the connection data set itself.

To have a plug that we can use in our controllers, we need to have call implemented with two arguments. First one is the connection and the second one is the options we pass. Since we don't care about the second argument, we just ignore it.

We try to get user_id from a session user_id = get_session(conn, :user_id). If the session is set, we find given user in the database in line assign(conn, :current_user, ElixirStream.Repo.get(ElixirStream.User, user_id)) and put it in to the connections assigns. This allows us to maintain current user across all the actions.

If we want to use this authentication mechanism and setting of the current_user, we just add this plug to our controller in following way:

  plug ElixirStream.Plugs.CheckAuthentication

Now, each controller action's conn will or will not contain current user in assings. We can access our assigns like so @conn.assigns[:current_user] which in turn will return nil or a %User{} struct containing logged in user.

So how can we use it in templates to see current user's avatar or username? The answer is view! In the web/views/layout_view.ex we define the following function:

  def current_user(conn) do
    conn.assigns[:current_user]
  end

If we want to get current user in view, we can do it in following way:

<%= current_user(@conn) %>  

This about concludes the brief overview of the registration process of http://elixirstream.com

Conclusions

What I love about Phoenix, is that it gives an ease of mind. All the things that are happening are under your nose and you don't get lost in all the objects that are flying around. I am looking at you Rails!
Even if documentation sometimes is scarce, it is a great pleasure to write a web app in Phoenix. It took some time to understand all the concepts behind it, but now that I've done the full cycle, the next one will be easier.

Love your craft!

P.S You can check the source code for ElixirStream here: https://github.com/janjiss/elixir-stream-phoenix
P.S.S I would like to thank my friend, Dainis Lapins, for helping me with this app.

Janis Miezitis

Read more posts by this author.

Subscribe to Janis Miezitis personal blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!