The Phoenix framework has rapidly gained popularity by providing the productivity of frameworks like Ruby on Rails while being among the most fastest frameworks. It disproves the notion that productivity must be sacrificed for performance.
What is Phoenix exactly?
Built with the Elixir programming language, Phoenix is a web framework. Based on the Erlang VM, Elixir is employed for creating low-latency, fault-tolerant, and distributed systems, all of which are essential characteristics of contemporary web applications. You can delve deeper into Elixir through this blog post or their official guide.
Ruby on Rails developers should definitely explore Phoenix due to its promised performance enhancements. Developers using other frameworks can also gain insights into Phoenix’s approach to web development.

This article highlights key considerations for Ruby on Rails developers transitioning to Phoenix.
The most significant difference you’ll likely encounter is that Elixir, unlike Ruby, is a functional programming language. However, there are several other key distinctions between these platforms that Phoenix learners should be aware of to maximize their productivity.
Naming conventions are more straightforward.
While seemingly minor, this difference can easily trip up Ruby on Rails developers.
Phoenix adopts a singular naming convention. For instance, you’d have “UserController” instead of “UsersController” as you would in Ruby on Rails. This applies universally except for database tables, which adhere to the plural form convention.
This might seem trivial after mastering the pluralization rules in Ruby on Rails, but it initially caused some confusion for me and many others learning Rails. Phoenix eliminates this potential point of confusion.
Routing is more manageable.
Routing in Phoenix and Ruby on Rails is quite similar. The primary difference lies in how request processing is controlled.
In Ruby on Rails (and other Rack applications), this is achieved through middleware. In contrast, Phoenix employs “plugs.”
Plugs handle connection processing.
For instance, the Rails::Rack::Logger middleware in Rails logs requests, a task accomplished by the Plug.Logger plug in Phoenix. Essentially, any functionality achievable with Rack middleware can be implemented using plugs.
Here’s a Phoenix router example:
| |
Let’s first examine the pipelines.
Think of these as middleware stacks, representing groups of plugs that the request will traverse. These can be used to ensure the request expects HTML, retrieve the session, and verify request security before it reaches the controller.
The ability to define multiple pipelines allows for selecting specific plugs for different routes. In our router, for example, we have separate pipelines for pages (HTML) and API (JSON) requests.
This flexibility in Phoenix acknowledges that using the exact same pipeline for different request types doesn’t always make sense.
Views and templates are distinct concepts.
Views in Phoenix differ from their counterparts in Ruby on Rails.
In Phoenix, views are responsible for rendering templates and providing functions that simplify raw data for template consumption. A Phoenix view most closely resembles a helper in Ruby on Rails, with the added responsibility of template rendering.
Let’s illustrate with an example that displays “Hello, World!” in the browser.
Ruby on Rails:
app/controllers/hello_controller.rb:
| |
app/views/hello/index.html.erb:
| |
Phoenix:
web/controllers/hello_controller.ex:
| |
web/views/hello_view.ex:
| |
web/templates/hello/index.html.eex:
| |
Both examples display “Hello, World!” in the browser but with key differences.
Firstly, unlike Ruby on Rails, Phoenix requires explicit specification of the template to render.
Secondly, Phoenix introduces a view layer between the controller and the template.
You might wonder about the purpose of an empty view. Behind the scenes, Phoenix compiles the template into a function roughly equivalent to this:
| |
The same result can be achieved by deleting the template and using the new render function. This proves useful when returning plain text or JSON.
Models are strictly for data modeling.
Phoenix models primarily focus on data validation, schema definition, relationships with other models, and data presentation.
While specifying the schema within the model might seem unusual at first, it facilitates the creation of “virtual” fields, which are not persisted to the database. Consider this example:
| |
Here, we define four fields in the “users” table: name, email, password, and password_hash.
The noteworthy aspect here is the “password” field, marked as “virtual.”
This allows setting and getting this field without persisting changes to the database. This is beneficial because we would have logic to hash the password and store the hash in the “password_hash” field before saving it to the database.
Although you still need to create a migration, the schema definition in the model is necessary because fields are not automatically loaded into the model as in Ruby on Rails.
The key distinction between Phoenix and Ruby on Rails is that the model doesn’t handle database persistence. This responsibility falls on a module called “Repo,” configured with database information, as demonstrated below:
| |
This code resides in environment-specific configuration files like config/dev.exs or config/test.exs. Consequently, we can use Repo for database operations like create and update.
| |
This is a common pattern in Phoenix controllers.
We provide a User with a name and email, and Repo attempts to create a new database record. As shown in the example, we can then optionally handle successful or failed attempts.
To grasp this code, understanding pattern matching in Elixir is crucial. The function returns a tuple containing two values: a status followed by either the model or a changeset. A changeset tracks changes and validates a model (more on changesets in the next section).
The first tuple, from top to bottom, matching the pattern of the tuple returned by the function that attempted the User insertion will execute its defined function.
Although we could have used a variable for the status instead of an atom (Elixir’s equivalent of a Ruby symbol), we would then match both success and failure cases, leading to the same function handling both scenarios. By specifying the desired atom to match, we define a function specifically for that status.
Ruby on Rails, through ActiveRecord, embeds the persistence functionality within the model. This increased responsibility can sometimes complicate model testing. Phoenix’s separation of concerns makes more sense and avoids bloating every model with persistence logic.
Changesets enable clear validation and transformation rules.
In Ruby on Rails, data validation and transformation, often handled through callbacks like “before_create” and validations, can be a breeding ground for elusive bugs. The lack of immediate transparency regarding when data transformations occur contributes to this.
Phoenix addresses this with changesets, providing explicit control over validations and transformations. This is one of my favorite Phoenix features.
Let’s augment our previous model to illustrate a changeset:
| |
The changeset here performs two actions.
First, it invokes the “cast” function, a whitelist of permitted fields similar to “strong_parameters” in Ruby on Rails. Next, it validates the presence of the “email” and “password” fields, rendering the “name” field optional. This ensures that users can only modify allowed fields.
The beauty of this approach lies in the ability to define multiple changesets, for example, separate ones for registration and user updates. We might choose to require the password field only during registration and not for updates.
Contrast this with the common practice in Ruby on Rails, where you’d have to specify validations to run only on “create”. In Rails, this can make it challenging to decipher the behavior of complex models.
Functionality import is straightforward yet flexible.
Many functionalities from a library called “Ecto” are imported into the models. You’ll typically find this line near the top of model files:
| |
The “HelloPhoenix.Web” module is located in “web/web.ex”. Within this module, you should find a function named “model” defined as follows:
| |
This snippet reveals the modules being imported from Ecto. You have the flexibility to add or remove modules as needed, and these changes will be reflected in all models.
Similar functions like “view” and “controller” serve the same purpose for their respective counterparts.
The quote and use keywords might seem puzzling. In this context, consider a quote as directly executing the code within it in the calling module’s context. So, it’s equivalent to having the quoted code written directly within the module.
Similarly, the use keyword allows code execution in the caller’s context. It essentially requires the specified module and then calls the __using__ macro on it, running the code in the caller’s context. For a deeper dive into quote and use, refer to the official guide.
This approach enhances code clarity by revealing the location of specific functions within the framework, reducing the perception of “magic” happening behind the scenes.
Concurrency is ingrained.
As a core feature of Elixir, concurrency is effortless in Phoenix. You gain an application capable of spawning multiple processes and running on multiple cores without concerns about thread safety or reliability.
Spawning a new process in Elixir is as simple as this:
| |
Everything between spawn and end will execute in a new process.
In fact, each request in Phoenix is handled within its own process. Elixir leverages the power of the Erlang VM to deliver reliable and efficient concurrency “for free.”
This makes Phoenix well-suited for running services that utilize WebSockets, which require persistent client-server connections (demanding applications capable of handling potentially thousands of concurrent connections).
While such requirements would significantly complicate a Ruby on Rails project, Phoenix effortlessly fulfills them through Elixir.
For incorporating WebSockets in your Phoenix application, you’ll need Channels. It’s analogous to ActionCable in Ruby on Rails but simpler to set up as it doesn’t necessitate a separate server.
Phoenix streamlines modern web app development.
While our focus has been on the differences, Phoenix shares some common ground with Ruby on Rails.
It roughly adheres to the MVC pattern, so understanding code organization should be fairly intuitive once you grasp the key distinctions. Phoenix also offers generators similar to Ruby on Rails for creating models, controllers, migrations, and more.
As you become more comfortable with Elixir and Phoenix, you’ll gradually approach Ruby on Rails levels of productivity.
The only times I feel less productive are when encountering a problem previously solved by a Ruby gem but lacking a corresponding Elixir library. Fortunately, the expanding Elixir community is steadily bridging these gaps.
Phoenix’s differences address many pain points associated with managing complex Ruby on Rails projects. While not a silver bullet for all issues, they guide you in the right direction. Choosing a slow framework for the sake of productivity is no longer justifiable; Phoenix provides the best of both worlds.