SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
5.
José Valim
Former Rails Core Team member.
He was trying to make Rails really thread
safe but... ended up creating a new
programming language (Elixir). Oops!
PerformanceProductivity
6.
Chris McCord
Author of render_sync a Ruby gem to
have real-time partials in Rails (before
ActionCable).
It got complicated and... he ended up
creating a new web framework
(Phoenix). Oops!
8.
Elixir is a dynamic, functional language
designed for building scalable and
maintainable applications.
Elixir leverages the Erlang VM, known for
running low-latency, distributed and
fault-tolerant systems.
24.
# /web/router.ex
defmodule TwitterDemo.Router do
use TwitterDemo.Web, :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 "/", TwitterDemo do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/timeline", PageController, :timeline
end
# Other scopes may use custom stacks.
# scope "/api", TwitterDemo do
# pipe_through :api
# end
end
# /config/routes.rb
Rails.application.routes.draw do
root 'page#index'
get '/timeline' => 'page#timeline'
end
25.
# /web/router.ex
defmodule TwitterDemo.Router do
use TwitterDemo.Web, :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 "/", TwitterDemo do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/timeline", PageController, :timeline
end
# Other scopes may use custom stacks.
# scope "/api", TwitterDemo do
# pipe_through :api
# end
end
# /config/routes.rb
Rails.application.routes.draw do
root 'page#index'
get '/timeline' => 'page#timeline'
end
26.
Plug
It’s an Elixir library that tries to solve the same problem than
Rack does for Ruby.
A plug is a function or module which always receives and returns
a connection, doing some data transformations in the middle.
When we compose multiple plugs we form a pipeline.
28.
# /web/router.ex
defmodule TwitterDemo.Router do
use TwitterDemo.Web, :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 "/", TwitterDemo do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/timeline", PageController, :timeline
end
# Other scopes may use custom stacks.
# scope "/api", TwitterDemo do
# pipe_through :api
# end
end
$ rails g controller Page index timeline
29.
# /app/controllers/page_controller.rb
class PageController < ApplicationController
def index
end
def timeline
end
end
# /web/controllers/page_controller.ex
defmodule TwitterDemo.PageController do
use TwitterDemo.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
def timeline(conn, params) do
conn
|> assign(:nickname, params["nickname"])
|> render("timeline.html")
end
end
30.
# /app/controllers/page_controller.rb
class PageController < ApplicationController
def index
end
def timeline
end
end
# /web/controllers/page_controller.ex
defmodule TwitterDemo.PageController do
use TwitterDemo.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
def timeline(conn, params) do
conn
|> assign(:nickname, params["nickname"])
|> render("timeline.html")
end
end
31.
Typical code in OOP / imperative programming:
people = DB.find_customers
orders = Orders.for_customers(people)
tax = sales_tax(orders, 2013)
filing = prepare_filing(tax)
We could rewrite it as...
filing = prepare_filing(
sales_tax(Orders.for_customers(
DB.find_customers), 2013))
Pipe Operator |>
32.
Pipe Operator |>
With Elixir pipe operator we can do just
filing = DB.find_customers
|> Orders.for_customers
|> sales_tax(2013)
|> prepare_filing
“|>” passes the result from the left expression as
the first argument to the right expression. Kinda
like the Unix pipe “|”. It’s just useful syntax sugar.
38.
# /web/models/message.ex
defmodule TwitterDemo.Message do
use TwitterDemo.Web, :model
@derive {Poison.Encoder, only: [:author, :content, :inserted_at]}
schema "messages" do
field :author, :string
field :content, :string
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params %{}) do
struct
|> cast(params, [:author, :content])
|> validate_required([:author, :content])
end
end
# /app/models/message.rb
class Message < ApplicationRecord
validates_presence_of :author, :content
end
39.
# /web/models/message.ex
defmodule TwitterDemo.Message do
use TwitterDemo.Web, :model
@derive {Poison.Encoder, only: [:author, :content, :inserted_at]}
schema "messages" do
field :author, :string
field :content, :string
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params %{}) do
struct
|> cast(params, [:author, :content])
|> validate_required([:author, :content])
end
end
# /app/models/message.rb
class Message < ApplicationRecord
validates_presence_of :author, :content
end
40.
Ecto
You could think about Ecto as “the ActiveRecord of Elixir”.
But better don’t. It’s not even an ORM (in its purest definition).
It’s a database wrapper and it’s main target it’s PostgreSQL.
Other database are supported too.
Main concepts behind Ecto are:
Schemas: each Model defines a struct with its schema.
Changesets: define a pipeline of transformations (casting, validation &
filtering) over our data before it hits the database.
43.
# /app/channels/timeline_channel.rb
# Be sure to restart your server when you modify this file.
Action Cable runs in a loop that does not support auto
reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
@nickname = params[:nickname]
stream_from "timeline"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def new_msg(payload)
# Careful with creating the record here
# http://www.akitaonrails.com/2015/12/28/fixing-dhh-s-
rails-5-chat-demo
message = Message.create!(content: payload['content'],
author: @nickname)
# DHH suggests doing this in a background job instead, I’m
not sure why?
ActionCable.server.broadcast 'timeline', message: message
end
end
$ mix phoenix.gen.channel Timeline
44.
# /web/channels/user_socket.ex
defmodule TwitterDemo.UserSocket do
use Phoenix.Socket
## Channels
channel "timeline:lobby", TwitterDemo.TimelineChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# ...
def connect(params, socket) do
{:ok, assign(socket, :nickname, params["nickname"])}
end
# ....
def id(_socket), do: nil
end
# /app/channels/timeline_channel.rb
# Be sure to restart your server when you modify this file.
Action Cable runs in a loop that does not support auto
reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
@nickname = params[:nickname]
stream_from "timeline"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def new_msg(payload)
# Careful with creating the record here
# http://www.akitaonrails.com/2015/12/28/fixing-dhh-s-
rails-5-chat-demo
message = Message.create!(content: payload['content'],
author: @nickname)
# DHH suggests doing this in a background job instead
ActionCable.server.broadcast 'timeline', message: message
end
end
45.
# /web/channels/timeline_channel.ex
defmodule TwitterDemo.TimelineChannel do
use TwitterDemo.Web, :channel
alias TwitterDemo.Message
def join("timeline:lobby", payload, socket) do
# Add authorization logic here as required.
{:ok, socket}
end
def handle_in("new_msg", %{"content" => content,}, socket) do
changeset = Message.changeset(%Message{}, %{
content: content,
author: socket.assigns.nickname
})
case Repo.insert(changeset) do
{:ok, message} ->
broadcast! socket, "new_msg", %{message: message}
{:noreply, socket}
{:error, _changeset} ->
{:reply, {:error, %{error: "Error saving the message"}},
socket}
end
end
def handle_out("new_msg", payload, socket) do
push socket, "new_msg", payload
{:noreply, socket}
end
end
# /app/channels/timeline_channel.rb
# Be sure to restart your server when you modify this file.
Action Cable runs in a loop that does not support auto
reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
@nickname = params[:nickname]
stream_from "timeline"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def new_msg(payload)
# Careful with creating the record here
# http://www.akitaonrails.com/2015/12/28/fixing-dhh-s-
rails-5-chat-demo
message = Message.create!(content: payload['content'],
author: @nickname)
# DHH suggests doing this in a background job instead
ActionCable.server.broadcast 'timeline', message: message
end
end
46.
# /web/channels/timeline_channel.ex
defmodule TwitterDemo.TimelineChannel do
use TwitterDemo.Web, :channel
alias TwitterDemo.Message
def join("timeline:lobby", payload, socket) do
# Add authorization logic here as required.
{:ok, socket}
end
def handle_in("new_msg", %{"content" => content,}, socket) do
changeset = Message.changeset(%Message{}, %{
content: content,
author: socket.assigns.nickname
})
case Repo.insert(changeset) do
{:ok, message} ->
broadcast! socket, "new_msg", %{message: message}
{:noreply, socket}
{:error, _changeset} ->
{:reply, {:error, %{error: "Error saving the message"}},
socket}
end
end
def handle_out("new_msg", payload, socket) do
push socket, "new_msg", payload
{:noreply, socket}
end
end
# /app/channels/timeline_channel.rb
# Be sure to restart your server when you modify this file.
Action Cable runs in a loop that does not support auto
reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
@nickname = params[:nickname]
stream_from "timeline"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def new_msg(payload)
# Careful with creating the record here
# http://www.akitaonrails.com/2015/12/28/fixing-dhh-s-
rails-5-chat-demo
message = Message.create!(content: payload['content'],
author: @nickname)
# DHH suggests doing this in a background job instead
ActionCable.server.broadcast 'timeline', message: message
end
end
47.
Pattern Matching
In Elixir: a = 1 does not mean we are assigning 1 to the variable a.
Instead of assigning a variable, in Elixir we talk about binding a variable .
The equal signs means we are asserting that the left hand side (LHS) is
equal to the right one (RHS). It’s like basic algebra.
iex> a = 1
1
iex> 1 = a
1
iex> [1, a, 3] = [1, 2, 3]
[1, 2, 3]
iex> a
2
48.
Pattern Matching
Function signatures use pattern matching.
Therefore we can have more than one signature.
defmodule Factorial do
def of(0), do: 1
def of(x), do: x * of(x-1)
end
look mum! programming without if - else
49.
# /web/channels/timeline_channel.ex
defmodule TwitterDemo.TimelineChannel do
use TwitterDemo.Web, :channel
alias TwitterDemo.Message
def join("timeline:lobby", payload, socket) do
# Add authorization logic here as required.
{:ok, socket}
end
def handle_in("new_msg", %{"content" => content,}, socket) do
changeset = Message.changeset(%Message{}, %{
content: content,
author: socket.assigns.nickname
})
case Repo.insert(changeset) do
{:ok, message} ->
broadcast! socket, "new_msg", %{message: message}
{:noreply, socket}
{:error, _changeset} ->
{:reply, {:error, %{error: "Error saving the message"}},
socket}
end
end
def handle_out("new_msg", payload, socket) do
push socket, "new_msg", payload
{:noreply, socket}
end
end
# /app/channels/timeline_channel.rb
# Be sure to restart your server when you modify this file.
Action Cable runs in a loop that does not support auto
reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
@nickname = params[:nickname]
stream_from "timeline"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def new_msg(payload)
# Careful with creating the record here
# http://www.akitaonrails.com/2015/12/28/fixing-dhh-s-
rails-5-chat-demo
message = Message.create!(content: payload['content'],
author: @nickname)
# DHH suggests doing this in a background job instead
ActionCable.server.broadcast 'timeline', message: message
end
end
Jose, Don’t forget to
mention OTP!
53.
1. Send history of messages when connecting to
channel.
2. Add Presence module (to display who is online).
3. Create a startup with this, become a unicorn
and profit!
* Only 1. & 2. are solved here
https://github.com/diacode/talkex/tree/feature/message-db-persistence
hint: it takes 10 minutes with phoenix v1.2
56.
EXPLICIT > IMPLICIT
or at least some reasonable balance
57.
“Functional Programming is about
making the complex parts of your
program explicit”
– José Valim
58.
Next steps (for you)
• Watch every talk by José Valim & Chris McCord
Really, you won’t regret.
• Books:
Programming Elixir – Dave Thomas
Programming Phoenix – Chris McCord, Bruce Tate & José Valim.
• Elixir Getting Started Guide (really good!)
http://elixir-lang.org/getting-started/introduction.html
• Phoenix Guide (really good!)
http://www.phoenixframework.org/docs/overview
• Elixir Radar (newsletter)
http://plataformatec.com.br/elixir-radar
• Madrid |> Elixir MeetUp
http://www.meetup.com/es-ES/Madrid-Elixir/
59.
THANK YOU
Questions?
Special thanks go to Diacode’s former team:
Victor Viruete, Ricardo García, Artur Chruszcz & Bruno Bayón
<3
[ now you can blink again ]