Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Comparing Phoenix with Ruby on Rails

Comparing Phoenix with Ruby on Rails

A talk showing how developers with Ruby on Rails background can start building Elixir applications with Phoenix Framework. We trace a parallel between similar components but also highlighting the main differences. Presented in Inaka/Erlang Solutions internal meetup.

Flávio Granero

April 30, 2016
Tweet

More Decks by Flávio Granero

Other Decks in Technology

Transcript

  1. www.erlang-solutions.com “ Most web frameworks make you choose between speed

    and a productive environment. Phoenix gives you both.
  2. www.erlang-solutions.com Framework Throughput (req/s) Latency (ms) Consistency (σ ms) Plug

    198328.21 0.63ms 2.22ms Phoenix 0.13.1 179685.94 0.61ms 1.04ms Play 171236.03 1.89ms 14.17ms Express Cluster 92064.94 1.24ms 1.07ms Martini 32077.24 3.35ms 2.52ms Sinatra 30561.95 3.50ms 2.53ms Rails 11903.48 8.50ms 4.07ms source: https://gist.github.com/omnibs/e5e72b31e6bd25caf39a
  3. www.erlang-solutions.com Chris McCord Phoenix Framework creator, author of Metaprogramming Elixir

    and Programming Phoenix. Worked for a Rails consulting for more than 4 years. THE MAIN CONTRIBUTORS José Valim Rails core-team member since 2010, creator of Elixir and Phoenix team member. Co-author of Programming Phoenix and Crafting Rails Apps.
  4. www.erlang-solutions.com PHOENIX APPLICATION DIRECTORIES . ├── README.md ├── _build ├──

    brunch-config.js ├── config ├── deps ├── lib ├── mix.exs ├── mix.lock ├── node_modules ├── package.json ├── priv ├── test └── web
  5. www.erlang-solutions.com PHOENIX APPLICATION DIRECTORIES . ├── _build ├── deps ├──

    mix.lock ├── brunch-config.js ├── node_modules ├── priv ├── package.json ├── README.md ├── mix.exs ├── config ├── test ├── lib └── web DEPENDENCIES ASSETS YOUR CODE
  6. www.erlang-solutions.com # mix.exs defmodule AppName.Mixfile do use Mix.Project def project

    do [app: :app_name, version: "0.0.1", elixir: "~> 1.0", elixirc_paths: elixirc_paths(Mix.env), compilers: [:phoenix, :gettext] ++ Mix.compilers, build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, aliases: aliases, deps: deps] end ... defp deps do [{:phoenix, "~> 1.1.4"}, {:postgrex, ">= 0.0.0"}, {:phoenix_ecto, "~> 2.0"}, {:phoenix_html, "~> 2.4"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.9"}, {:cowboy, "~> 1.0"}] end ... end MIX.EXS
  7. www.erlang-solutions.com # mix.exs (part 2) defmodule AppName.Mixfile do use Mix.Project

    ... def application do [mod: {AppName, []}, applications: [:phoenix, :phoenix_html, :cowboy, : logger, :gettext, :phoenix_ecto, :postgrex, :timex, : httpotion]] end end MIX.EXS
  8. www.erlang-solutions.com PHOENIX APPLICATION DIRECTORIES web ├── channels │ └── user_socket.ex

    ├── controllers │ └── page_controller.ex ├── gettext.ex ├── models ├── router.ex ├── static │ ├── assets │ ├── css │ ├── js │ └── vendor ├── templates │ ├── layout │ └── page ├── views │ ├── error_helpers.ex │ ├── error_view.ex │ ├── layout_view.ex │ └── page_view.ex └── web.ex
  9. www.erlang-solutions.com PHOENIX SCAFFOLD $ rails scaffold User name:string email: string

    $ mix phoenix.gen.html User users name: string email:string
  10. www.erlang-solutions.com USER MODEL # web/models/user.ex defmodule AppName.User do use AppName.Web,

    :model schema "users" do field :name, :string field :email, :string timestamps end @required_fields ~w(name email) @optional_fields ~w() def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) end end
  11. www.erlang-solutions.com USER MODEL # web/models/user.ex defmodule AppName.User do use AppName.Web,

    :model schema "users" do field :name, :string field :email, :string timestamps end @required_fields ~w(name email) @optional_fields ~w() def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) |> validate_format(:email, ~r/@/) end end
  12. www.erlang-solutions.com ROUTER # web/router.ex defmodule AppName.Router do use AppName.Web, :router

    pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end scope "/", AppName do pipe_through :browser get "/", PageController, :index end end
  13. www.erlang-solutions.com ROUTER # web/router.ex defmodule AppName.Router do use AppName.Web, :router

    pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end scope "/", AppName do pipe_through :browser get "/", PageController, :index resources "/users", UserController end end
  14. www.erlang-solutions.com ROUTER # web/router.ex defmodule AppName.Router do use AppName.Web, :router

    pipeline :browser do plug :accepts, ["html"] ... end pipeline :auth do plug :user_required end scope "/", AppName do pipe_through :browser pipe_through :auth get "/", PageController, :index resources "/users", UserController end end
  15. www.erlang-solutions.com # web/controllers/user_controller.ex defmodule AppName.UserController do use AppName.Web, :controller alias

    AppName.User plug :scrub_params, "user" when action in [:create, :update] def index(conn, _params) do users = Repo.all(User) render(conn, "index.html", users: users) end def show(conn, %{"id" => id}) do user = Repo.get!(User, id) render(conn, "show.html", user: user) end ... USER CONTROLLER
  16. www.erlang-solutions.com “ The connection is a struct containing information about

    the request. Each layer of Phoenix makes a little change and when done, that connection will have the response in it.
  17. www.erlang-solutions.com # web/controllers/user_controller.ex defmodule AppName.UserController do ... def new(conn, _params)

    do changeset = User.changeset(%User{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"user" => user_params}) do ... end def edit(conn, %{"id" => id}) do ... end def delete(conn, %{"id" => id}) do ... end ... end USER CONTROLLER
  18. www.erlang-solutions.com # web/controllers/user_controller.ex defmodule AppName.UserController do ... def update(conn, %{"id"

    => id, "user" => user_params}) do user = Repo.get!(User, id) changeset = User.changeset(user, user_params) case Repo.update(changeset) do {:ok, user} -> conn |> put_flash(:info, "User updated successfully.") |> redirect(to: user_path(conn, :show, user)) {:error, changeset} -> render(conn, "edit.html", user: user, changeset: changeset) end end ... end USER CONTROLLER
  19. www.erlang-solutions.com # web/views/user_view.ex defmodule AppName.UserView do use AppName.Web, :view alias

    AppName.User def title(%User{name: name, email: email}) do "#{name} <#{email}>" end def render("show.json", %{user: user}) do %{id: user.id, name: user.name, email: user.email} end end USER VIEW AND TEMPLATE # web/templates/user/new.html.eex <h2><%= title(@user) %></h2> <%= render "form.html", changeset: @changeset, action: user_path(@conn, :create) %> <%= link "Back", to: user_path(@conn, :index) %>
  20. www.erlang-solutions.com <%= form_for @changeset, @action, fn f -> %> <%=

    if @changeset.action do %> <div class="alert alert-danger"> <p>Oops, something went wrong! Please check the errors below.</p> </div> <% end %> <div class="form-group"> <%= label f, :name, class: "control-label" %> <%= text_input f, :name, class: "form-control" %> <%= error_tag f, :name %> </div> <div class="form-group"> <%= label f, :email, class: "control-label" %> <%= email_input f, :email, class: "form-control" %> <%= error_tag f, :email %> </div> <div class="form-group"> <%= submit "Submit", class: "btn btn-primary" %> </div> <% end %> USER VIEW AND TEMPLATE
  21. www.erlang-solutions.com ~/app_name mix phoenix.server [info] Running AppName.Endpoint with Cowboy using

    http on port 4000 13 Apr 09:59:18 - info: compiled 5 files into 2 files, copied 3 in 748ms [info] GET / [debug] Processing by AppName.PageController.index/2 Parameters: %{} Pipelines: [:browser] [info] Sent 200 in 63ms [info] GET /users [debug] Processing by AppName.UserController.index/2 Parameters: %{} Pipelines: [:browser] [debug] SELECT u0."id", u0."name", u0."email", u0." inserted_at", u0."updated_at" FROM "users" AS u0 [] OK query=0.3ms [info] Sent 200 in 960µs PHOENIX SERVER
  22. www.erlang-solutions.com Write the logic to connect in the rooms:lobby channel

    # web/channels/room_channel.ex defmodule HelloPhoenix.RoomChannel do use Phoenix.Channel def join("rooms:lobby", _message, socket) do {:ok, socket} end def join("rooms:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end
  23. www.erlang-solutions.com Handle new new_msg events on Phoenix # web/channels/room_channel.ex defmodule

    HelloPhoenix.RoomChannel do use Phoenix.Channel def join("rooms:lobby", _message, socket) do {:ok, socket} end def join("rooms:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end end
  24. www.erlang-solutions.com Edit socket.js to join rooms:lobby channel and uncomment import

    socket from app.js // web/static/js/socket.js ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("rooms:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket // web/static/js/app.js ... import socket from "./socket"
  25. www.erlang-solutions.com Send a message to new_msg event import socket from

    "./socket"; ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("rooms:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) channel.push("new_msg", {body: "Hello people"}) export default socket
  26. www.erlang-solutions.com Handle new new_msg events on JavaScript // web/static/js/socket.js ...

    socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("rooms:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) channel.push("new_msg", {body: "Hello people"}) channel.on("new_msg", payload => { console.log(payload.body) }) export default socket
  27. www.erlang-solutions.com TOOLS MiniTest, RSpec ExUnit, ESpec Rubocop, HoundCI Credo, Credo

    Server Resque, Sidekiq Exq ActionMailer Swoosh, Bamboo RestClient, Her HTTPoison, Dayron