ELIXIR FOR RUBYISTS
RUBYCONF BANGLADESH 2017
MY NAME IS TAWSIF
AND I AM NOT A RUBYIST
RUBY == 😍
RUBY
Syntax
Happiness
Productivity
Community
Tooling
TDD
Rails
MODERN WEB
Concurrency
Performance
Scalability
Microservice
Real-Time Connection
Distributed System
Fault Tolerance
Created by Joe Armstrong at Ericsson
Used by Telecom Companies
For Scalable, Reliable Systems
Fault Tolerant
High Availability
WHATSAPP
2 million connections in a single node.
ERLANG == 😰
ERLANG
Unfriendly Syntax
Difficult Error Reporting
Macros are Unreadable
Lack of Toolings
JOSÉ VALIM
Created Elixir
Ex-member of Rails Core Team
Creator of Devise
ELIXIR == 😃
ELIXIR
Ruby Like Syntax
Based on Erlang
Runs in Erlang VM (EVM)
Can use Erlang libraries
Functional
Concurrent
Performance
“Elixir is what would happen if Erlang,
Clojure and Ruby somehow had a
baby and it wasn’t an accident”
Devin Torres
RUBY VS ELIXIR
irb
rake
bundler
gem
Polymorphism
Lazy Enumerables
Metaprogramming
iex
mix
mix
hex
Protcols
Streams
Macros
RUBY ELIXIR
RUBY HAS RAILS
ELIXIR HAS PHOENIX
http://www.phoenixframework.org
RAILS VS PHOENIX
rails new
rails server
rails generate *
Sprockets
ActiveRecord
ERB
ActionCable
Test::Unit
mix phoenix.new
mix phoenix.server
mix phoenix.gen.*
Brunch
Ecto
EEX
Channels
ExUnit
PHOENIX
source 'https://rubygems.org'
gem 'rails', '~> 5.0.2'
gem 'sqlite3'
gem 'puma', '~> 3.0'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
group :development do
gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen'
end
DEPENDENCY MANAGER
Gemfile
RUBY
defmodule Blog.Mixfile do
use Mix.Project
defp deps do
[{:phoenix, "~> 1.2.1"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.0"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"}]
end
end
DEPENDENCY MANAGER
mix
ELIXIR
Rails.application.routes.draw do
get “/”, to: “welcome#index”
resources :posts do
resources :comments
end
end
ROUTER
RUBY
defmodule Blog.Router do
use Blog.Web, :router
get "/", WelcomeController, :index
resources "/posts", PostController do
resources “/comments”, CommentController
end
end
end
ROUTER
ELIXIR
class CommentsController < ApplicationController
def create
Post.create! post_params
redirect_to @post
end
private
def post_params
params.required(:post).permit(:title, :body)
end
end
CONTROLLER
RUBY
defmodule Blog.PostController do
use Blog.Web, :controller
alias Blog.Post
def create(conn, %{"post" => post_params}) do
changeset = Post.changeset(%Post{}, post_params)
case Repo.insert(changeset) do
{:ok, _post} ->
conn
|> put_flash(:info, "Post created.")
|> redirect(to: post_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
end
CONTROLLER
ELIXIR
class Post < ApplicationRecord
has_many :comments
validates :title, presence: true
validates :body, presence: true
end
MODEL
RUBY
defmodule Blog.Post do
use Blog.Web, :model
schema "posts" do
field :title, :string
field :body, :string
has_many :comments, Blog.Comment
timestamps()
end
def changeset(struct, params  %{}) do
struct
|> cast(params, [:title, :body])
|> validate_required([:title, :body])
end
end
MODEL
ELIXIR
<h1>Editing Post</h1>
<%= link_to 'Show', @post %>
<%= link_to 'Back', posts_path %>
<%= form_for(post) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :body %>
<%= f.text_area :body %>
<%= f.submit %>
</form>
VIEW
RUBY
<h2>Editing Post</h2>
<%= link "Show", to: post_path(@conn, :show, @post.id) %>
<%= link "Back", to: post_path(@conn, :index) %>
<%= form_for @changeset, @action, fn f -> %>
<%= label f, :title %>
<%= text_input f, :title %>
<%= label f, :body%>
<%= textarea f, :body %>
<%= submit "Submit"%>
<% end %>
VIEW
ELIXIR
class CreatePosts < ActiveRecord::Migration[5.0]
def change
create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
MIGRATION
RUBY
defmodule Blog.Repo.Migrations.CreatePost do
use Ecto.Migration
def change do
create table(:posts) do
add :title, :string
add :body, :text
timestamps()
end
end
end
MIGRATION
ELIXIR
class RoomsChannel < ApplicationCable::Channel
def subscribed
@topic = "rooms:#{rand(1..8)}"
stream_from @topic
end
def publish_msg(data)
ActionCable.server.broadcast(@topic,
body: data["body"],
username: data["username"],
started: data["started"]
)
end
end
CHANNELS
RUBY
defmodule Chat.RoomChannel do
use Chat.Web, :channel
def join("room:" <> _id, _params, socket) do
{:ok, socket}
end
def handle_in("publish_msg", %{"body" => body, "user" => user}, socket) do
broadcast!(socket, "new_message", %{body: body, user: user})
{:reply, :ok, socket}
end
end
CHANNELS
ELIXIR
class PostsControllerTest < ActionDispatch::IntegrationTest
setup do
@post = posts(:one)
end
test "should get index" do
get posts_url
assert_response :success
end
test "should get new" do
get new_post_url
assert_response :success
end
end
UNIT TEST
RUBY
defmodule Blog.PostControllerTest do
use Blog.ConnCase
alias Blog.Post
@valid_attrs %{body: "body", title: "title"}
@invalid_attrs %{}
test "lists all entries on index", %{conn: conn} do
conn = get conn, post_path(conn, :index)
assert html_response(conn, 200) =~ "Listing posts"
end
test "renders form for new resources", %{conn: conn} do
conn = get conn, post_path(conn, :new)
assert html_response(conn, 200) =~ "New post"
end
end
UNIT TEST
ELIXIR
title
|> String.downcase
|> String.replace(~r/W/, " ")
|> String.split
|> Enum.join("-")
BONUS (PIPE OPERATOR)
ELIXIR
RUBY
title.
downcase.
gsub(/W/, " ").
split.
join("-")
title
|> downcased
|> non_words_to_spaces
|> whitespace_to_dashes
BONUS (PIPE OPERATOR)
ELIXIR
RUBY
text.
downcase.
replace_non_words_with_spaces.
drop_extra_whitespace.
join_with_dashes
BONUS (OBSERVER)
:observer.start()
BONUS (OBSERVER)
:observer.start()
BONUS (OBSERVER)
:observer.start()
wrk -t4 -c100 -d30S --timeout 2000
https://github.com/mroth/phoenix-showdown/blob/master/RESULTS_v3.md
BENCHMARK
Framework Req/sec Latency
Gin (Go) 59001.07 1.84
Plug (Elixir) 53815.76 2.67
Phoenix (Elixir) 31417.81 3.52
Express Cluster (Node) 26244.35 3.92
Martini (Go) 12493.48 10.15
Sinatra (Rails) 8334.84 7.56
Rails (Rails) 3452.58 17.96
https://dockyard.com/blog/2016/08/09/phoenix-channels-vs-rails-action-cable
BENCHMARK (CHANNEL)
Rails: 75 Rooms, 3750 users
RUBY
https://dockyard.com/blog/2016/08/09/phoenix-channels-vs-rails-action-cable
BENCHMARK (CHANNEL)
Phoenix: 1100 rooms, 55,000 users
ELIXIR
WHEN TO USE ELIXIR?
High-availability Systems
Real-time Connection
Scale
“If your only tool is hammer, then
every problem looks like a nail”
An old adage
https://github.com/h4cc/awesome-elixir
🙌
QUESTION?
👏
THANKS

RubyConf Bangladesh 2017 - Elixir for Rubyists