Elixir: the new kid in town
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
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.
And finally, the Phoenix framework
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:
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:
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:
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:
Run mix phoenix.server
to start your server, go to localhost:4000
and you will see the following screen:
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:
- fetching the OMDB API
- handling the response
- 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:
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.
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.
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 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:
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
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.
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:
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.
When we put all these functions together, we get our API wrapper complete!
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!
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.
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.