We love Ruby. We have enough experience with Ruby and web software development. But something that we love even more is being up to date. We are always trying new libraries, frameworks, designs and languages too. We like to get our own experience, contribute to as many open source projects we can. This is something that we enjoy, individually and as a team. It is part of the company’s culture.

Personally I like to start with new languages, see how they evolve, how the community grows and how they tackle the old and the new challenges. I found Elixir to be a programming language which will play an important role in the future, where parallel computing is a bottleneck for Ruby.

Lets take a look to the tools that Elixir offers.

What is Elixir?

Elixir is a dynamic, functional language created by José Valim which runs on top of the Erlang Virtual Machine.

Elixir isn’t the CoffeeScript of Erlang just as Clojure isn’t the CoffeeScript of Java.

Elixir provides extra features like macros, better string handling than Erlang, a decent package manager, metaprogramming and a beautiful syntax.

If syntax wasn’t important, then we’d all still be using Roman numerals

Joe Armstrong - “The mess we’re in”

Concurrent programming is known to be difficult, and there’s a performance penalty to pay when you create lots of processes. Elixir doesn’t have these issues, thanks to the architecture of the Erlang VM on which it runs. However, we will leave that subject for future posts and focus on our first steps using Elixir.

Dummy Application

For better understanding, let’s build a dummy application which parses an external API response.

Use omdb API to search movies by title, parse the JSON response and show the relevant movie’s attributes with a nice view.

To accomplish this, we are going to use the Phoenix framework which is also maintained by the creators of Elixir.

Installation

I’m using OS X, so the installation is very straightforward

$ brew install elixir

When you install Elixir, besides getting the elixir, elixirc and iex executables, you also get an executable Elixir script named mix. mix is a build tool which provides tasks for creating, compiling, testing your application, managing its dependencies and much more.

Given we want to compile some assets and Phoenix uses brunch.io to do it, you will probably need to install Node.js too.

$ brew install node

And finally, the Phoenix framework

$ mix local.hex
$ mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.15.0/phoenix_new-0.15.0.ez

Creating the application

Bootstrap

We can run mix phoenix.new from any directory in order to bootstrap our Phoenix application. Phoenix will accept either an absolute or relative path for the directory of our new project. Assuming that the name of our application is random_movies, either of these will work:

$ mix phoenix.new /tmp/random_movies

$ mix phoenix.new random_movies

In Phoenix, Ecto is a database wrapper and language integrated query based in the Repository pattern which IMHO is way better than ActiveRecord (the most common pattern used in Ruby web applications).

Given our application will fetch the data from an external API, we don’t need the persistence layer, so we can pass --no-ecto to opt out of Ecto:

$ mix phoenix.new random_movies --no-ecto
$ cd random_movies
$ mix do deps.get, compile

For those who come from Rails or MVC-based applications they won’t find anything new because Phoenix uses the MVC pattern too.

Controllers and Views

Phoenix controllers act as intermediary modules. Their functions - called actions - are invoked from the router in response to HTTP requests.

Every application created by mix phoenix.new will have:

  • A controller, the PageController
  • A view, the PageView
  • A template, index.html.eex

Phoenix assumes a strong naming convention from controllers to views to the templates they render. The PageController requires a PageView to render templates in the web/templates/page directory.

Phoenix views have two main jobs:

  • Rendering templates (this includes layouts).
  • Providing functions which take raw data and make it easier for templates to consume.

For templates, Phoenix uses EEx which is part of Elixir. EEx is quite similar to ERB in Ruby.

As I mentioned before, we want to display the movie poster and most relevant attributes. Let’s assume the controller give us a Map named @movie with the attributes of the movie we want to show.

Our template should look something like this:

<h2 class="text-center">
  <%= Map.get(@movie, :Title) %> - <span><%= Map.get(@movie, :Year) %></span>
</h2>

<h3 class="text-center"> <%= Map.get(@movie, :Director) %> </h3>

<section id=<%= Map.get(@movie, :imdbID) %> class="text-center">
  <main>
    <img src="<%= Map.get(@movie, :Poster) %>">
    <p><%= Map.get(@movie, :Plot) %></p>
  </main>
</section>

For simplicity, we will overwrite the web/templates/page/index.html.eex file with this content.

Now, we need to actually send the movie Map from the controller. Open the page_controller.ex file and edit the index function like this:

def index(conn, _params) do
  movie = %{
    "Title": "Goodfellas",
    "Year": "1990",
    "Director": "Martin Scorsese",
    "Plot": "Henry Hill and his friends work their way up through the mob hierarchy.",
    "Poster": "https://ia.media-imdb.com/images/M/MV5BMTY2OTE5MzQ3MV5BMl5BanBnXkFtZTgwMTY2NTYxMTE@._V1_SX300.jpg",
    "imdbID": "tt0099685"
  }

  render conn, "index.html", movie: movie
end

Run mix phoenix.server to start your server, go to localhost:4000 and you will see the following screen:

RandomMovies front page

Congratulations! You have an Elixir application running on your machine! It was pretty simple, wasn’t it?

I know, we cheated, there is no API parsing. But I didn’t tell you we had finished.

So far, it looks like a Rails application with a few small differences. It is time to get our hands dirty.

API wrapper

When you deal with an external API, the best practice is to have a wrapper to contain the logic associated in one place. That is what we are going to build.

When compiling, Elixir will look for source files in our lib directory, as long as the file extensions are correct (standard is *.ex for files meant for compilation), and create the corresponding BEAM bytecode files.

We’re going to follow the convention and place our Omdb module file in lib/random_movies.

This module is responsible of:

  1. fetching the OMDB API
  2. handling the response
  3. converting the JSON object into a Map

Elixir does not provide an HTTP client on its toolset, but there is a library which suits perfectly for our needs: httpoison.

Adding a dependency to our project is as simple as adding the library in the deps list in the mix.exs file. It should look something like this:

defp deps do
  [{:phoenix, "~> 0.15"},
   {:phoenix_html, "~> 1.4"},
   {:phoenix_live_reload, "~> 0.5", only: :dev},
   {:cowboy, "~> 1.0"},
   {:httpoison, "~> 0.7"}]
end

In order to run the httpoison application when our server starts, just add :httpoison at the end of the applications list in the same file.

Now we are ready to tackle the three responsibilities of the Omdb module.

1. Fetching the OMDB API

We will receive the title to search from the controller, but only the title. We want to convert that string into a valid API resource.

According to the omdb documentation, we need to do a simple GET to the URL.

def find_by_title_url(title) do
  "https://omdbapi.com/?t=#{title}"
end

def fetch(title) do
  url = find_by_title_url(title)
  response = HTTPoison.get(url, [{"User-agent", "Elixir"}])

  handle_response response
end

If you noticed, this looks exactly as a ruby method, except for the do keyword. Although it is completely valid Elixir code, this is not the best way to write it.

The following snippet uses a better syntax that Elixir provides, using the pipe operator.

def fetch(title) do
  find_by_title_url(title)
  |> HTTPoison.get([ {"User-agent", "Elixir"} ])
  |> handle_response
end

The |> symbol used in the snippet above is the pipe operator: it simply takes the output from the expression on its left side and passes it as the input to the function call on its right side. It’s similar to the Unix | operator. Its purpose is to highlight the flow of data being transformed by a series of functions.

2. Handling JSON response

To tackle the second step, I need to introduce you a not-so-new concept: Pattern Matching

Pattern Matching allows developers to easily destructure data types such as tuples and lists. It is one of the foundations of recursion in Elixir and applies to other types as well, like maps and binaries. Basically it lets you write different forms of a function that code can match against, and the compiler will invoke the first definition that matches the pattern.

That being said, we want to apply Pattern Matching on the response.

HTTPoison.get/2 return value is a tuple of two elements. The first one is an atom (known as symbols in rubyland) representing the status of the call. This is really helpful to identify a failed call using pattern matching. This will be our first pattern matching.

defp handle_response({ :error, response }) do
  ...
end

defp handle_response({ :ok, response }) do
  ...
end

defp is a keyword to define private functions.

The code above handles two different return values. If HTTPoison.get/2 returns an error, Elixir will run the first definition, otherwise the second one.

In case we get a successful response from the API, we are able to pattern match on the status code, body, or any other part of the response making the code more readable and declarative, like the following snippet:

defp handle_response({ :ok, %HTTPoison.Response{status_code: 200, body: body} }) do
  ...
end

defp handle_response({ :ok, %HTTPoison.Response{status_code: 404, body: body} }) do
  ...
end

Unfortunately, the OMDB API responses are 200 even when the element is not found. That is why we can’t use the pattern matching on the status_code, instead we are going to match the body to decide if it was found or not.

On the other hand, on error responses, we want to return a tuple with the atom :error and the reason.

Wrapping up, we get

defp handle_response({:ok, %HTTPoison.Response{body: body}}) do
  case body do
    "{\"Response\":\"False\",\"Error\":\"Movie not found!\"}" ->
      {:error, "Not found"}
    _ ->
      decode_as_movie body
  end
end

defp handle_response({:error, %HTTPoison.Error{reason: reason}}) do
  {:error, reason}
end

3. Converting JSON to Movie Struct

Finally we got here! We hit the API, we got a successful result and we have a valid JSON waiting to be parsed and displayed in our front page.

This is one of the most straightforward steps, thanks to Phoenix. A library called poison is included in the framework and it allows us to encode and decode JSON strings.

defp decode_as_movie(body) do
  Poison.decode body
end

That would be enough. However, I would like to go one step further and create a model Movie which declares the specific attributes we want to keep. At the beginning of the article I told you we are not going to use ecto, instead we will define a struct to represent a movie.

To achieve this, we create a new file web/models/movie.ex and copy the following code in it:

defmodule RandomMovies.Movie do
  defstruct [:Title, :Year, :imdbID, :Plot, :Poster, :Director]
end

Now we are ready to decode the JSON response and convert it into a Movie. Given this is a common use case, poison introduces an option to do it.

defp decode_as_movie(body) do
  Poison.decode body, as: Movie
end

When we put all these functions together, we get our API wrapper complete!

defmodule RandomMovies.Omdb do
  @user_agent [ {"User-agent", "Elixir"} ]

  def fetch(title) do
    find_by_title_url(title)
    |> HTTPoison.get(@user_agent)
    |> handle_response
  end

  defp find_by_title_url(title) do
    "https://omdbapi.com/?t=#{title}"
  end

  defp handle_response({:ok, %HTTPoison.Response{body: body}}) do
    case body do
      "{\"Response\":\"False\",\"Error\":\"Movie not found!\"}" ->
        {:error, "Not found"}
      _ ->
        decode_as_movie body
    end
  end

  defp handle_response({:error, %HTTPoison.Error{reason: reason}}) do
    {:error, reason}
  end

  defp decode_as_movie(body) do
    Poison.decode body, as: RandomMovies.Movie
  end
end

I extracted the user_agent into a variable to improve the readability of the code.

There is only one thing left to do: take the argument from the URL and call the API.

In the web/router.ex file, replace

get "/", PageController, :index

with this other line, which basically adds a name to the parameter

get "/:query", PageController, :index

Back in the PageController, we need to update the index action to accept a parameter and to call the Omdb wrapper instead of hardcoding a movie.

But at this time, you will find that you know how to do it… pattern matching!

def index(conn, %{"query" => query}) do
  case RandomMovies.Omdb.fetch(query) do
    {:ok, movie} ->
      render conn, "index.html", movie: movie
    {:error, _} ->
      render conn, "index.html", query: query, movie: nil
  end
end

Finally, we get a title to search from the URL, we call the API, parse the response and return a movie, or an error if it wasn’t found. To reflect this in our outdated template, we can add a simple if statement and restart our server to see it working.

<%= if is_nil @movie do %>
  <h2 class="text-center">
    No movies with title "<%= @query %>"
  </h2>
<% else %>
  <h2 class="text-center">
    <%= Map.get(@movie, :Title) %> - <span><%= Map.get(@movie, :Year) %></span>
  </h2>

  <h3 class="text-center"> <%= Map.get(@movie, :Director) %> </h3>

  <section id=<%= Map.get(@movie, :imdbID) %> class="text-center">
    <main>
      <img src="<%= Map.get(@movie, :Poster) %>">
      <p><%= Map.get(@movie, :Plot) %></p>
    </main>
  </section>
<% end %>

Use the application

Play around with the dummy application we built. Here is a list of my favorite movies if you are lazy and don’t want to look out for movies:

Source Code

If you just want to review the code, or download and run the server immediately here is the source code.