Phoenix for Rubyists
Phoenix Framework draws heavily upon important foundations in the opinionated web frameworks that came before it, like Ruby on Rails.
1 — The First Sip
Before we jump right into the framework, we need to at least know the basics of the programming language we’re working with.Agenda
Welcome & Getting Started
We’ll go over our agenda, and set our sights on some goals for the day.
Origins, Foundations & Core Principles
Elixir is unique, in that it provides us the fantastic ergonomics of a modern programming language, while standing on the solid and battle-tested foundation of the Erlang ecosystem.
Elixir’s interactive shell (IEx) is one of the most powerful tools in your toolbox. We’ll outline some of the most useful features for beginners, including
- Running scripts
- Getting metadata about a value
- Accessing embedded documentation
- Inspecting the state of a particular process
IO & Files
As with most programming languages, it’s useful to know how to interact with files and humans. We’ll take care of this early on, and notice a few things that foreshadow some interesting aspects of Elixir’s concurrency model.
2 — Types, Operators & Control Flow
One must crawl before one walks, and it all starts with basic types and procedural logic. Even if you’re experienced in a wide range of programming languages, there’s going to be a lot of stuff – even at this basic level – that may change the way you look at writing code forever.Agenda
Math & Strings
There’s no getting away from these kinds of things. Eventually you’re going to need to work with numbers and text, so we’ll start with a crash course in some core APIs (including a dip in the erlang pool) that will make life easy.
There’s a lot of capability here, but we’ll stay close to the commonly-useful and pragmatic path.
EXERCISE: Projectile Motion
We’ll create a simple program that calculates and object’s projectile motion, given a launch angle and initial velocity
EXERCISE: String Acrobatics
We’ve got a bunch of functions that do various things to strings, but our tests are failing.
It stands to reason that functions are really important in a functional programming language. We’ll build and work with named an anonymous functions, combine functions together to form pipelines, and even map out some higher-order functions of our own.
Break for Lunch
Tuples and Lists
Often times we find ourselves needing to work with several objects in a “collection”, and will need to choose between Elixir’s List and Tuple types. We’ll compare and contrast tuples and lists, and write a few programs highlighting the benefits of each
Associative Data Structures
We have two main associative data structures in Elixir: keyword lists and maps. Let’s learn more about them!
EXERCISE: Building up a List
Assembling a bunch of items in a list is really fast, as long as we do it in a way that doesn’t involve moving existing items around in memory. We’ll write two programs, one which assembles a bunch of dictionary words into a tuple, and another that uses a list instead.
Pattern Matching & Guards
This modern language feature allows destructed assignment, and is often used in order to define several variants of a function, each to handle a specific scenario. This application of pattern matching reduces what would otherwise be a lot of internal function complexity by huge amounts.
EXERCISE: Function Refactoring
We’ve got an elixir module that involves some code that could benefit from some pattern matching magic. Refactor the monolith function so all use of if/else are replaced by creating new functions oriented toward handling that specific pattern of arguments.
Conditionals & Guards
It’s unusual to use if/else in Elixir, because we have some far more powerful approaches to deciding among different branches of code to use. We’ll look at the
case control flow structure, where pattern matching really starts to shine. We’ll also take a look at how guards can be added to
case clauses (and other control flow structures) to form even more specific and targeted patterns.
EXERCISE: More Refactoring
We’ll refactor some more code to leverage the power of pattern matching and functional control flow tools.
Wrap up for the day
3 — Writing Modular Programs
Elixirs module system allows us to define layers of related functions. In this part of the course, we’ll explore the concepts of modules, and the ability to reference code in one module from another.Agenda
Modules & Three Important Directives
Modules are just a group of several functions, some of which may be private and some of which may be public. Modules give us the ability to define named functions using the
def macro, which offer a few other features that were unavailable in the world of anonymous functions
EXERCISE: Mission Control
We’ve got a set of tests for a couple of Elixir modules that are used to control a space ship. Alter the code to make the unit tests pass, and ensure that you’ve kept as much of each module’s internal functionality private as possible.
use macro is not strictly a directive, it’s of particular importance when considering “mixins” for common functionaliy across multiple modules.
EXERCISE: Extending a module
use macro can essentially be used to decorate a module with some code from another
4 — Working With Data Structures
Earlier we outlined and worked with several different types of data structures. Let’s take a closer look at some waysAgenda
EXERCISE: Map, Filter, Reduce
We have a program that starts with a list of objects read from a file. Using the built-in functions available in the
Map modules, filter out “inactive” items (objects where the “active” attribute is not
true), and then log a list of object names to the console.
Taming List Enumeration with Comprehensions
Often we find ourselves looping over something enumerable, mapping values into another list, and potentially filtering out some unwanted items. Comprehensions use a generator and a filter to provide some excellent syntactic sugar for this kind of task.
Take another pass at the previous exercise, and use a comprehension to devise a concise solution.
Break for Lunch
5 — Request, Response
A Phoenix app can basically be boiled down to a function that receives a HTTP request, and returns a response. We’ll begin with this premise, and start to understand the important parts involved in this process.Agenda
Endpoint & Routing
Requests enter your app through an Endpoint, and your app usually will have only one. We’ll look at this chain of Elixir Plugs, which ends at the Router, the module ultimately responsible for delegating request-handling to an appropriate Controller.
Plugs & Pipelines
Plugs are at the core of Phoenix, and they’re a relatively simple and approachable concept: things that accept a connection as an argument, and return a slightly-modified connection.
Chain a few plugs together, and it’s easy to see how basic building blocks start to assemble into a complete application.
EXERCISE: Routing to the Pages controller
Add a new page to your app, following the existing example set up in your PageController
EXERCISE: Hating on a Content-Type
Build a Plug that interrupts the pipeline (returning a HTTP error for an incoming request) if we ever request a SOAP XML document (
The Controller Responds
Now that we understand how to leverage Phoenix’s routing layer, let’s take a closer look at Controllers: the modules ultimately responsible for responding to a request.
Views & Templates
In a welcome contrast to other web frameworks, Phoenix’s view layer is exceedingly easy to understand and use.
Judging by how easy it is to keep views simple, performant, and easy to manage, It’s clear that the hard-learned lessons from older frameworks have paid off.
EXERCISE: Assigns & Functional Views
We’ll pass some data from the Phoenix controller layer to the view layer, and leverage view functions to do some light formatting & massaging.
6 — Testing
Testing ergonomics is perhaps the most impactful factor in determining whether writing tests is an enjoyable part of day-to-day development, or an annoying slog that’s neglected until problems arise.
In this area, Phoenix does not disappoint. We’ll focus on several useful patterns for unit and acceptance testing, with the aim of making tests quick, easy, maintainable and intuitive.Agenda
Controller and View Tests
Sometimes we use Phoenix to render HTML, so we’ll look at how we can verify that both our controller and view layers (individually) are doing their job.
JSON API Tests
Often we use Phoenix Controllers to render JSON. We’ll explore some built-in helpers that are well-suited for helping us write tests verifying that the JSON contains what we expect, and touch on a few libraries that make this even easier!
7 — Managing Data
Data is an integral part of virtually any web application, and a great persistence library make a huge difference in performance and maintainability.
Thankfully, the Elixir ecosystem has us covered in spades. Ecto is a thin layer of functions that allow us to build composable queries, validate fields, and seamlessly transform records between our DB and application representations.Agenda
Intro to Ecto
Heavy persistence libraries like ActiveRecord offer convenience, but often become performance bottlenecks. We could make every DB query explicitly, but then we’re trading in all of our ergonomics for performance.
Ecto manages to strike an enjoyable balance, where we are asked to be deliberate about the records we fetch from a database but (most of the time) aren’t dragged into the world of writing SQL queries explicitly.
You’ll be amazed at how much we can do with just simple functions, and will never look at other persistence frameworks quite the same way again.
If you’ve never used code to manage changes to your database schema, you’re missing out. Migrations allow us to change our schema in (ideally) reversible steps, so we can apply and un-apply a set of changes while building features. Even if you’ve seen migrations before, there are some useful things to know about how they work with Ecto, and in particular, Postgres. We’ll look, specifically at:
- Postgres array and jsonb column types
- Changing column types, while remaining backwards compatible
This is one of my favorite parts about Ecto, and one of the parts you’ll be most often working with. In contrast to other persistence libraries, the concept of the shape of a record (schema) and the logic for checking the validity of values (validations) are decoupled. There are some incredibly exciting consequences of this design decision.
Ecto ships with a bunch of validations, and because it’s so quick and easy, we’ll write a few of our own.
EXERCISE: Models & Validation
Create models and appropriate validations for a blog post/comment app.
While we could use SQL syntax to retrieve records from our database, doing so would open us up to a world of pain. Ecto provides an approachable and composable way of building queries, while stopping short of doing us any “automatic favors” (i.e., N+1 queries) that so often degrade performance.
EXERCISE: Query Olympics
You’ll be given a list of database queries for you and your classmates to make using Ecto. Each query is worth a certain number of points. Highest number of points after the exercise is done, wins!
Break for Lunch
8 — Real Time
One of the places where Elixir and Phoenix leave the competition in the dust is support for soft real time programming. The ability to keep a lightweight Elixir process running for the duration of a user’s time in our app, and holding some small amount of state, makes things far simpler for certain things than it otherwise would be.Agenda
Phoenix Channels are a first class citizen in the framework, on equal footing with Controllers. It shows! You’ll be amazed at how easy it is to start adding real-time features to your apps, where we push data from server to client.
Development best practices are increasingly moving in a functional and “stateless” direction, but Elixir Processes are a place where small pieces of state can be safely held and used. We’ll explore how powerful this idea is, in the context of Phoenix channels.
EXERCISE: My First Channel
Build your first channel, so that you can notify client-side apps of new comments being posted to articles.
Managing Channel Complexity
While you may have contributed to a REST API project that had 10 endpoints (each handling 1-4 HTTP verbs), it’s less likely that you have experience working with a long-lived web socket connection operating on the same scale of complexity. It’s important to remember that this is API surface, and because it’s often stateful instead of stateless, keeping organized is even more important.
9 — Users & Authentication
Nearly every app we build these days requires some sort of authentication, and probably a user account to go along with it. Even if your app is an oddball and doesn’t need this, user accounts provide us with a well-understood set of use cases that will serve as an excellent case study.
Let’s put some of our newfound Phoenix knowledge into practice as we implement a secure user account feature set. The goal will be to reduce explicit management of authorization & authentication on a per-resource basis as much as possible.Agenda
Creating new users will serve to highlight a few concepts at the model layer
- Server-side validation, including writing our own validator
- Safely handling passwords
- Keeping slightly different changeset-generating functions organized
We’ll also have an opportunity to start defining routes that require a user to be authenticated, and routes that don’t.
EXERCISE: Login & Logout
For our purposes, we’ll use a JSON Web Token (JWT) and the OAuth 2 password grant standard, as a mechanism and vehicle for authentication. You will be provided with a client-side app that will talk to our Phoenix, via JSON.
We’ll validate a user’s credentials in a way that’s not incredibly sensitive to timing or brute force attacks, and then assemble our little piece of session state (the JWT) before encrypting it and handing it back to the client.
We often have a concept of roles (or an equivalent concept masquerading as other flags/fields) built on top of our authentication. We’ll add roles to our JWT, and design a plug that will raise an error if a user attempts to access a controller action without having the required roles.
Wrap Up & Goodbye
We’ll recap everything we’ve learned