SlideShare une entreprise Scribd logo
1  sur  66
Télécharger pour lire hors ligne
PHOENIX
FOR RAILS DEVS
Conferencia Rails
Madrid
15/10/2016
If you're having talk problems,
I feel bad for you, son.
I got 61 problems, but a slide
ain't one; hit me!
About me:
Javier Cuevas
@javier_dev
RUBY ON RAILS SHOP
WHO EMBRACED ELIXIR
AIRBNB FOR DOGS
“MAJESTIC” RAILS 3.2 MONOLITH
GUESTS
OF THE DAY
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
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!
WHAT IS
ELIXIR?
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.
WHAT IS
PHOENIX?
Phoenix is a productive web framework
for Elixir that does not compromise speed
and maintainability.
PHOENIX = PRODUCTIVITY + PERFORMANCE
PERFORMANCE
I don’t care about performance.
* that much
PRODUCTIVITY
SHORT
TERM
LONG
TERM
SHORT TERM
PRODUCTIVITY
The Phoenix Backpack
• Mix (generators, tasks, etc.)
• Erlang libraries + Hex.pm
• ES6 out of the box
• Live reload
• Nice error pages
• Concurrent test tools +
DocTests
• Great docs (for real)
• Channels + Presence
• OTP: humongous set of
libraries for distributed
computing
• Erlang observer
• ....
Remember the “15 min blog” by DHH?
That was productivity!
Let’s try build the “15 min real time Twitter”
(or something close to).
https://github.com/javiercr/conferencia_ror_demo
LET’S
GET STARTED
rails new twitter_demo mix phoenix.new twitter_demo
!"" twitter_demo
#"" app
$   #"" assets
$   #"" channels
$   #"" controllers
$   #"" helpers
$   #"" jobs
$   #"" mailers
$   #"" models
$   !"" views
#"" bin
#"" config
#"" db
#"" lib
#"" log
#"" public
#"" test
#"" tmp
!"" vendor
!"" twitter_demo
#"" config
#"" deps
#"" lib
#"" node_modules
#"" priv
#"" test
!"" web
#"" channels
#"" controllers
#"" models
#"" static
#"" templates
!"" views
$ cd twitter_demo
$ bundle install
$ rake db:create
$ rails server
$ cd twitter_demo
$ mix deps.get && npm install
$ mix ecto.create
$ mix phoenix.server
ROUTES
# /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
# /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
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.
CONTROLLER
# /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
# /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
# /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
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 |>
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.
VIEWS /
TEMPLATES
<!-- /app/views/page/index.html.erb -->
<h1>Welcome to TwitterDemo!</h1>
<%= form_tag timeline_path, method: "get" do %>
<label for="nickname">Nickname</label>:
<input type="text" name="nickname"></input>
<button>Connect!</button>
<% end %>
<!-- /web/templates/page/index.html.eex -->
<h1>Welcome to TwitterDemo!</h1>
<%= form_tag(page_path(@conn, :timeline), method: "get") do %>
<label for="nickname">Nickname</label>:
<input type="text" name="nickname"></input>
<button>Connect!</button>
<% end %>
<!-- /app/views/page/timeline.html.erb -->
<script>window.nickname = "<%= @nickname %>";</script>
<div id="messages"></div>
<input id="chat-input" type="text"></input>
<!-- /web/templates/page/timeline.html.eex -->
<script>window.nickname = "<%= @nickname %>";</script>
<div id="messages"></div>
<input id="chat-input" type="text"></input>
MODEL
$ rails g model Message author:string
content:text
$ rake db:migrate
$ mix phoenix.gen.model Message messages
author:string content:text
$ mix ecto.create && mix ecto.migrate
# /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
# /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
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.
CHANNEL
$ rails g channel Timeline new_msg $ mix phoenix.gen.channel Timeline
# /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
# /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
# /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
# /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
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
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
# /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!
JAVASCRIPT
# /app/assets/javascripts/channels/timeline.coffee
App.timeline = App.cable.subscriptions.create {channel:
"TimelineChannel", nickname: window.nickname},
connected: ->
# Called when the subscription is ready for use on the
server
chatInput = document.querySelector("#chat-input")
chatInput.addEventListener "keypress", (event) =>
if event.keyCode == 13
@new_msg chatInput.value
chatInput.value = ""
received: (payload) ->
@_renderdMessage(payload.message)
new_msg: (message) ->
@perform 'new_msg', {content: message}
_renderdMessage: (message) ->
# [...]
messagesContainer.appendChild(messageItem)
// /web/static/js/socket.js
import {Socket} from "phoenix"
let socket = new Socket("/socket", {
params: { token: window.userToken, nickname:
window.nickname }})
socket.connect()
let channel = socket.channel("timeline:lobby", {})
let chatInput = document.querySelector("#chat-input")
let renderMessage = (message) => {
// [...]
messagesContainer.appendChild(messageItem)
}
chatInput.addEventListener("keypress", event => {
if(event.keyCode === 13){
channel.push("new_msg", {content: chatInput.value})
chatInput.value = ""
}
})
channel.on("new_msg", payload => {
renderMessage(payload.message)
})
channel.join()
export default socket
HOMEWORK
(for you)
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
tl;dr
$ rails new my_project $ mix phoenix.new my_project
$ rails g [x] $ mix phoenix.gen.[x]
$ bundle install $ mix deps.get
$ rake db:migrate $ mix ecto.migrate
$ rails server $ mix phoenix.server
$ rails console $ iex -S mix
bundle + rake Mix
RubyGems Hex.pm
Rack Plug
Minitest / RSpec ExUnit
ActiveRecord Ecto
ActionCable Channels + Presence
sprockets Brunch (npm based)
Redis / Sidekiq / Resque OTP
LONG TERM
PRODUCTIVY
EXPLICIT > IMPLICIT
or at least some reasonable balance
“Functional Programming is about
making the complex parts of your
program explicit”
– José Valim
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/
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 ]

Contenu connexe

Tendances

Bootstrap your Cloud Infrastructure using puppet and hashicorp stack
Bootstrap your Cloud Infrastructure using puppet and hashicorp stackBootstrap your Cloud Infrastructure using puppet and hashicorp stack
Bootstrap your Cloud Infrastructure using puppet and hashicorp stack
Bram Vogelaar
 

Tendances (20)

Ansible with AWS
Ansible with AWSAnsible with AWS
Ansible with AWS
 
Ansible and AWS
Ansible and AWSAnsible and AWS
Ansible and AWS
 
Testing Ansible with Jenkins and Docker
Testing Ansible with Jenkins and DockerTesting Ansible with Jenkins and Docker
Testing Ansible with Jenkins and Docker
 
Developing Terraform Modules at Scale - HashiTalks 2021
Developing Terraform Modules at Scale - HashiTalks 2021Developing Terraform Modules at Scale - HashiTalks 2021
Developing Terraform Modules at Scale - HashiTalks 2021
 
DevOps in a Regulated World - aka 'Ansible, AWS, and Jenkins'
DevOps in a Regulated World - aka 'Ansible, AWS, and Jenkins'DevOps in a Regulated World - aka 'Ansible, AWS, and Jenkins'
DevOps in a Regulated World - aka 'Ansible, AWS, and Jenkins'
 
London Hug 19/5 - Terraform in Production
London Hug 19/5 - Terraform in ProductionLondon Hug 19/5 - Terraform in Production
London Hug 19/5 - Terraform in Production
 
An Introduction to Kube-Lego
An Introduction to Kube-LegoAn Introduction to Kube-Lego
An Introduction to Kube-Lego
 
Ansible Intro - June 2015 / Ansible Barcelona User Group
Ansible Intro - June 2015 / Ansible Barcelona User GroupAnsible Intro - June 2015 / Ansible Barcelona User Group
Ansible Intro - June 2015 / Ansible Barcelona User Group
 
ElixirConf Lightning Talk: Elixir |> Production
ElixirConf Lightning Talk: Elixir |> ProductionElixirConf Lightning Talk: Elixir |> Production
ElixirConf Lightning Talk: Elixir |> Production
 
Automating aws infrastructure and code deployments using Ansible @WebEngage
Automating aws infrastructure and code deployments using Ansible @WebEngageAutomating aws infrastructure and code deployments using Ansible @WebEngage
Automating aws infrastructure and code deployments using Ansible @WebEngage
 
Cachopo - Scalable Stateful Services - Madrid Elixir Meetup
Cachopo - Scalable Stateful Services - Madrid Elixir MeetupCachopo - Scalable Stateful Services - Madrid Elixir Meetup
Cachopo - Scalable Stateful Services - Madrid Elixir Meetup
 
Ansible 2 and Ansible Galaxy 2
Ansible 2 and Ansible Galaxy 2Ansible 2 and Ansible Galaxy 2
Ansible 2 and Ansible Galaxy 2
 
Spinnaker 파트 1
Spinnaker 파트 1Spinnaker 파트 1
Spinnaker 파트 1
 
Create Development and Production Environments with Vagrant
Create Development and Production Environments with VagrantCreate Development and Production Environments with Vagrant
Create Development and Production Environments with Vagrant
 
Cocoapods and Most common used library in Swift
Cocoapods and Most common used library in SwiftCocoapods and Most common used library in Swift
Cocoapods and Most common used library in Swift
 
Kube-AWS
Kube-AWSKube-AWS
Kube-AWS
 
Bootstrap your Cloud Infrastructure using puppet and hashicorp stack
Bootstrap your Cloud Infrastructure using puppet and hashicorp stackBootstrap your Cloud Infrastructure using puppet and hashicorp stack
Bootstrap your Cloud Infrastructure using puppet and hashicorp stack
 
Akka.net versus microsoft orleans
Akka.net versus microsoft orleansAkka.net versus microsoft orleans
Akka.net versus microsoft orleans
 
What's new in Ansible 2.0
What's new in Ansible 2.0What's new in Ansible 2.0
What's new in Ansible 2.0
 
I can't believe it's not a queue: Kafka and Spring
I can't believe it's not a queue: Kafka and SpringI can't believe it's not a queue: Kafka and Spring
I can't believe it's not a queue: Kafka and Spring
 

Similaire à Phoenix for Rails Devs

Similaire à Phoenix for Rails Devs (20)

Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Rack
 
Intro to Rack
Intro to RackIntro to Rack
Intro to Rack
 
RoR 101: Session 2
RoR 101: Session 2RoR 101: Session 2
RoR 101: Session 2
 
Ruby on Rails - Introduction
Ruby on Rails - IntroductionRuby on Rails - Introduction
Ruby on Rails - Introduction
 
Dev streams2
Dev streams2Dev streams2
Dev streams2
 
Crossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkCrossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end Framework
 
Wider than rails
Wider than railsWider than rails
Wider than rails
 
Introduction to Ruby on Rails
Introduction to Ruby on RailsIntroduction to Ruby on Rails
Introduction to Ruby on Rails
 
Rails web api 开发
Rails web api 开发Rails web api 开发
Rails web api 开发
 
Rails Engine | Modular application
Rails Engine | Modular applicationRails Engine | Modular application
Rails Engine | Modular application
 
Rails missing features
Rails missing featuresRails missing features
Rails missing features
 
RubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteRubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - Keynote
 
Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...
 
Example Cosmos SDK Application Tutorial
Example Cosmos SDK Application TutorialExample Cosmos SDK Application Tutorial
Example Cosmos SDK Application Tutorial
 
Workshop 16: EmberJS Parte I
Workshop 16: EmberJS Parte IWorkshop 16: EmberJS Parte I
Workshop 16: EmberJS Parte I
 
Supa fast Ruby + Rails
Supa fast Ruby + RailsSupa fast Ruby + Rails
Supa fast Ruby + Rails
 
Curso rails
Curso railsCurso rails
Curso rails
 
Divide and Conquer – Microservices with Node.js
Divide and Conquer – Microservices with Node.jsDivide and Conquer – Microservices with Node.js
Divide and Conquer – Microservices with Node.js
 
Building Cloud Castles
Building Cloud CastlesBuilding Cloud Castles
Building Cloud Castles
 
Alberto Maria Angelo Paro - Isomorphic programming in Scala and WebDevelopmen...
Alberto Maria Angelo Paro - Isomorphic programming in Scala and WebDevelopmen...Alberto Maria Angelo Paro - Isomorphic programming in Scala and WebDevelopmen...
Alberto Maria Angelo Paro - Isomorphic programming in Scala and WebDevelopmen...
 

Plus de Diacode

Presentación de Kogi
Presentación de KogiPresentación de Kogi
Presentación de Kogi
Diacode
 

Plus de Diacode (11)

Startup nomads
Startup nomadsStartup nomads
Startup nomads
 
Ruby on Rails & TDD con RSpec
Ruby on Rails & TDD con RSpecRuby on Rails & TDD con RSpec
Ruby on Rails & TDD con RSpec
 
Hacking your bank with Ruby and reverse engineering (Madrid.rb)
Hacking your bank with Ruby and reverse engineering (Madrid.rb)Hacking your bank with Ruby and reverse engineering (Madrid.rb)
Hacking your bank with Ruby and reverse engineering (Madrid.rb)
 
TLKR.io @ Betabeers Madrid
TLKR.io @ Betabeers MadridTLKR.io @ Betabeers Madrid
TLKR.io @ Betabeers Madrid
 
Métricas para hacer crecer tu proyecto
Métricas para hacer crecer tu proyectoMétricas para hacer crecer tu proyecto
Métricas para hacer crecer tu proyecto
 
Métricas para hacer crecer tu proyecto
Métricas para hacer crecer tu proyectoMétricas para hacer crecer tu proyecto
Métricas para hacer crecer tu proyecto
 
Presentación de Kogi
Presentación de KogiPresentación de Kogi
Presentación de Kogi
 
Educación: The Next Big Thing
Educación: The Next Big ThingEducación: The Next Big Thing
Educación: The Next Big Thing
 
Front-End Frameworks: a quick overview
Front-End Frameworks: a quick overviewFront-End Frameworks: a quick overview
Front-End Frameworks: a quick overview
 
Taller de Introducción a Ruby on Rails (2ª parte)
Taller de Introducción a Ruby on Rails (2ª parte)Taller de Introducción a Ruby on Rails (2ª parte)
Taller de Introducción a Ruby on Rails (2ª parte)
 
Taller de Introducción a Ruby on Rails
Taller de Introducción a Ruby on RailsTaller de Introducción a Ruby on Rails
Taller de Introducción a Ruby on Rails
 

Dernier

%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
masabamasaba
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
chiefasafspells
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
masabamasaba
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
masabamasaba
 

Dernier (20)

WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 

Phoenix for Rails Devs

  • 1. PHOENIX FOR RAILS DEVS Conferencia Rails Madrid 15/10/2016
  • 2. If you're having talk problems, I feel bad for you, son. I got 61 problems, but a slide ain't one; hit me!
  • 3. About me: Javier Cuevas @javier_dev RUBY ON RAILS SHOP WHO EMBRACED ELIXIR AIRBNB FOR DOGS “MAJESTIC” RAILS 3.2 MONOLITH
  • 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.
  • 10. Phoenix is a productive web framework for Elixir that does not compromise speed and maintainability.
  • 11. PHOENIX = PRODUCTIVITY + PERFORMANCE
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19. I don’t care about performance. * that much
  • 22. The Phoenix Backpack • Mix (generators, tasks, etc.) • Erlang libraries + Hex.pm • ES6 out of the box • Live reload • Nice error pages • Concurrent test tools + DocTests • Great docs (for real) • Channels + Presence • OTP: humongous set of libraries for distributed computing • Erlang observer • ....
  • 23. Remember the “15 min blog” by DHH? That was productivity! Let’s try build the “15 min real time Twitter” (or something close to).
  • 26. rails new twitter_demo mix phoenix.new twitter_demo
  • 27. !"" twitter_demo #"" app $   #"" assets $   #"" channels $   #"" controllers $   #"" helpers $   #"" jobs $   #"" mailers $   #"" models $   !"" views #"" bin #"" config #"" db #"" lib #"" log #"" public #"" test #"" tmp !"" vendor !"" twitter_demo #"" config #"" deps #"" lib #"" node_modules #"" priv #"" test !"" web #"" channels #"" controllers #"" models #"" static #"" templates !"" views
  • 28. $ cd twitter_demo $ bundle install $ rake db:create $ rails server $ cd twitter_demo $ mix deps.get && npm install $ mix ecto.create $ mix phoenix.server
  • 29.
  • 31. # /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
  • 32. # /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
  • 33. 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.
  • 35. # /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
  • 36. # /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
  • 37. # /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
  • 38. 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 |>
  • 39. 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.
  • 41. <!-- /app/views/page/index.html.erb --> <h1>Welcome to TwitterDemo!</h1> <%= form_tag timeline_path, method: "get" do %> <label for="nickname">Nickname</label>: <input type="text" name="nickname"></input> <button>Connect!</button> <% end %> <!-- /web/templates/page/index.html.eex --> <h1>Welcome to TwitterDemo!</h1> <%= form_tag(page_path(@conn, :timeline), method: "get") do %> <label for="nickname">Nickname</label>: <input type="text" name="nickname"></input> <button>Connect!</button> <% end %>
  • 42. <!-- /app/views/page/timeline.html.erb --> <script>window.nickname = "<%= @nickname %>";</script> <div id="messages"></div> <input id="chat-input" type="text"></input> <!-- /web/templates/page/timeline.html.eex --> <script>window.nickname = "<%= @nickname %>";</script> <div id="messages"></div> <input id="chat-input" type="text"></input>
  • 43. MODEL
  • 44. $ rails g model Message author:string content:text $ rake db:migrate $ mix phoenix.gen.model Message messages author:string content:text $ mix ecto.create && mix ecto.migrate
  • 45. # /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
  • 46. # /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
  • 47. 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.
  • 49. $ rails g channel Timeline new_msg $ mix phoenix.gen.channel Timeline
  • 50. # /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
  • 51. # /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
  • 52. # /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
  • 53. # /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
  • 54. 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
  • 55. 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
  • 56. # /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!
  • 58. # /app/assets/javascripts/channels/timeline.coffee App.timeline = App.cable.subscriptions.create {channel: "TimelineChannel", nickname: window.nickname}, connected: -> # Called when the subscription is ready for use on the server chatInput = document.querySelector("#chat-input") chatInput.addEventListener "keypress", (event) => if event.keyCode == 13 @new_msg chatInput.value chatInput.value = "" received: (payload) -> @_renderdMessage(payload.message) new_msg: (message) -> @perform 'new_msg', {content: message} _renderdMessage: (message) -> # [...] messagesContainer.appendChild(messageItem) // /web/static/js/socket.js import {Socket} from "phoenix" let socket = new Socket("/socket", { params: { token: window.userToken, nickname: window.nickname }}) socket.connect() let channel = socket.channel("timeline:lobby", {}) let chatInput = document.querySelector("#chat-input") let renderMessage = (message) => { // [...] messagesContainer.appendChild(messageItem) } chatInput.addEventListener("keypress", event => { if(event.keyCode === 13){ channel.push("new_msg", {content: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { renderMessage(payload.message) }) channel.join() export default socket
  • 60. 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
  • 61. tl;dr $ rails new my_project $ mix phoenix.new my_project $ rails g [x] $ mix phoenix.gen.[x] $ bundle install $ mix deps.get $ rake db:migrate $ mix ecto.migrate $ rails server $ mix phoenix.server $ rails console $ iex -S mix bundle + rake Mix RubyGems Hex.pm Rack Plug Minitest / RSpec ExUnit ActiveRecord Ecto ActionCable Channels + Presence sprockets Brunch (npm based) Redis / Sidekiq / Resque OTP
  • 63. EXPLICIT > IMPLICIT or at least some reasonable balance
  • 64. “Functional Programming is about making the complex parts of your program explicit” – José Valim
  • 65. 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/
  • 66. 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 ]