Phoenix for Rails developers: a practical example - Part 1

brunvez

Bruno Vezoli

Posted on September 28, 2020

Phoenix for Rails developers: a practical example - Part 1

This is an introductory post to Phoenix, Elixir's web framework. It is not intended to be a complete guide since it’s a big framework with lots of things to dive into, but rather to show a side by side comparison of how things are done in both frameworks using the scaffolding they provide to throw some insight into their best practices with a classic blog example.

It is a two-part blog post, in the first half we'll take a look at the web layer where both frameworks are most alike and in the second half, we'll dive into the business layer which is where the frameworks diverge.

The idea is to show how similar they are and get you hyped enough to try it yourself!

Introduction

I started working on Ruby after an exploratory phase of many languages led me to it. The syntax just captivated me, it was so expressive that it could be read like English and it had so many tools that allowed me to do things more productively than ever before. No more string helper functions or awkward loops to manipulate arrays, you could have all those things out of the box and just focus on coding your solution. After a while I found Rails, and it felt right in with Ruby’s philosophy. I felt much more productive than with other tools and it came with batteries included. No hours of configurations or research for a library that just required you to do all the work.

Since that time I’ve tried many other web development frameworks in a wide variety of languages but I never felt at home with any of them. Even if many tried to replicate what Rails did there was always something missing, whether it was because of the language or some decision where it diverged they always felt like mere copies to me.

Phoenix

Enter Phoenix, a web framework built on top of Elixir, a functional language with a Ruby-like syntax that runs on top of the Erlang virtual machine. This framework took after many of the features that make Rails what it is: MVC architecture, an opinionated file structure, included tools such as an ORM, a template engine and a routing layer. But it never tried to be another copy, instead, it diverged with time and took many decisions that fit better the functional paradigm and the different language, all without sacrificing productivity or developer happiness.

In time, this led to an independent web framework but with that special flavor we all love from Rails. There’s one difference though, since Elixir was built on top of Erlang, which was designed to build massively scalable applications, all apps built with Phoenix inherit this perk. This means that you can code away without having to worry about how your app will perform once you reach the big leagues. This is something with which many Rails apps struggle and it’s the main reason why Jose Valim developed Elixir in the first place.

But enough preaching, let’s see it in action!

File structure

No opinionated framework would be complete without a take on how files should be organized. So let's first take a look at how things look from the folder view. This is what you get when you generate an app with both frameworks:

Alt Text

Phoenix's structure inherits from how packages (Elixir's equivalent for gems) are structured, all custom code goes inside the lib/ folder. Inside it you'll find right away the main difference between both frameworks in this aspect: Phoenix separates the layer of web presentation from the business logic. In the image above, we have a blog/ folder, which contains the basic modules to build our business logic and the blog_web which looks pretty similar to the Rails' app/ directory. There are some subtle differences though but we'll take a deeper look into the them in the following sections.

Scaffold away

As I mentioned, both frameworks provide commands to scaffold different parts of the application. For this example, we'll compare the output of generating a full resource including views and routes, but first, the commands used:

bin/rails generate scaffold Article title:string text:text
Enter fullscreen mode Exit fullscreen mode
mix phx.gen.html Articles Article articles title:string text:text
Enter fullscreen mode Exit fullscreen mode

Once more they look alike with subtle differences, in this case, Phoenix goes for a more verbose fashion by asking us to specify the context module where it'll be created, the name of the schema module (equivalent to the model) and its plural to be used as the table name.

Routes

Every web request starts by being routed to the correct handler, let's go with the simple routes first and see how Rails handles this:

Rails.application.routes.draw do
  resources :articles
end
Enter fullscreen mode Exit fullscreen mode

Rails' routes are pretty straightforward once you get to know them, they just specify which endpoints are available for the end user, allow gems (or more precisely engines and plugins) to mount their routes and some validation like Devise's authenticate method. The manipulation of the request is done on the controllers and from time to time it's a bit difficult to know where a value came from, who parsed a request or who rejected it. Phoenix has a different take on this:

defmodule BlogWeb.Router do
  use BlogWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", BlogWeb do
    pipe_through :browser

    resources "/articles", ArticleController
    get "/", PageController, :index
  end


  scope "/api", BlogWeb do
    pipe_through :api
  end
end
Enter fullscreen mode Exit fullscreen mode

That's a lot more code, but just because there are more responsibilities too. The plug calls you see here define something that could be compared to middlewares, they get executed serially in the order they are defined. They get grouped together with the pipeline function so they can be called inside scopes (which are just like Rails'), to perform things like parsing, authentication, authorization and pretty much anything else you might want to do before an action gets executed.

This allows decoupling controllers from the logic of authorizing users and rejecting requests most of the time, which substitutes before_action calls and, in many cases, reduces the need for complex inheritance between controllers.

Controllers

Next step is the actual handling, since both fameworks follow the MVC pattern this is done in the controllers. For these I decided just to show some actions and do a quick walkthrough of the differences, there is a lot to understand on functional programming to decipher what's Phoenix doing and this is not the post for that. If you feel interested to learn some more on Elixir's syntax the Elixir Getting Started Guide is a good place to start. Without further ado, I present to you the controllers:

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show,
                                     :edit,
                                     :update,
                                     :destroy]

  def index
    @articles = Article.all
  end

 def new
    @article = Article.new
  end

 def create
    @article = Article.new(article_params)

    respond_to do |format|
      if @article.save
        format.html { redirect_to @article }
        format.json { render :show, status: :created,
                             location: @article }
      else
        format.html { render :new }
        format.json { render json: @article.errors,
                             status: :unprocessable_entity }
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode
defmodule BlogWeb.ArticleController do
  use BlogWeb, :controller

  alias Blog.Articles
  alias Blog.Articles.Article

  def index(conn, _params) do
    articles = Articles.list_articles()
    render(conn, "index.html", articles: articles)
  end

  def new(conn, _params) do
    changeset = Articles.change_article(%Article{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"article" => article_params}) do
    case Articles.create_article(article_params) do
      {:ok, article} ->
        conn
        |> put_flash(:info, "Article created successfully.")
        |> redirect(to: Routes.article_path(conn, :show, article))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

The main difference here is just the programming paradigm. Elixir is functional so the request (or connection) and the params are received as parameters of the action, while Ruby, being object oriented, provides the same data through local state and accessor functions. Still, the syntax is quite similar and I think it's easy to get a grasp of what's going on in the Phoenix snippet.

Another small difference is that the generator used on Phoenix will scaffold a controller that just responds to HTML, while Rails' will render views for both JSON and HTML. There is, of course, another generator to make a JSON resource but it'll create a new controller which will go on a different pipeline (if you go back to the routes section you'll see there's an :api pipeline).

Helpers and Views

Last but not least is how we present the data on the web layer. You probably noticed in the file structure that there were two directories called views/ and templates/ on the Phoenix project structure, if you didn't go and take a closer view. You might be wondering what's the difference between them, well the templates are just that, text files with embedded code (just like erb we have eex) which you use to respond to requests. Views, on the other hand, resemble to helpers but with one noticeable difference: they are not global by default.

Let's take a look at how they differ with an example; we would like to show the read time in our articles. Since we don't track this value on our DB and it's just for presentation you'd normally want to put this on a helper, like this:

module ArticlesHelper
  WORDS_PER_MINUTE = 200

  def read_time(article)
    words_count = article.text.split.size
    read_time_in_minutes = words_count / WORDS_PER_MINUTE.to_f
    #{read_time_in_minutes.ceil} minutes"
  end
end
Enter fullscreen mode Exit fullscreen mode

Easy peasy, now we just have to call this on our show.html.erb and we are done. Not so fast, the problem I see with this is that it gets included in every view. Sure, for a small app like this and with a name so specific, there's no problem, but when it starts to grow and you have helpers with names like format_title and format_title_for_user you start having troubles. Phoenix's helpers (or views as they are called in the framework) are very similar:

defmodule BlogWeb.ArticleView do
  use BlogWeb, :view

  alias Blog.Articles.Article

  @words_per_minute 200

  defp read_time(%Article{text: text}) do
    words_count =
      text
      |> String.split(" ")
      |> Enum.count()
    read_time_in_minutes = Float.ceil(words_count / @words_per_minute)
    "#{trunc(read_time_in_minutes)} minutes"
  end
end
Enter fullscreen mode Exit fullscreen mode

But as I said before they are scoped to some presenter so this view, for example, has functions that can only be called on an article template. This allows you to use whatever name you want knowing you won't pollute the global scope. And what about if you want to use common functions across many templates? Simple, make another module with those functions and include it across the views where you need it.

Follow up

This concludes the short walkthrough of the difference in the web layer. Next up, in the second part of this post is the data and business layer.

💖 💪 🙅 🚩
brunvez
Bruno Vezoli

Posted on September 28, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related