Rails to Phoenix

Ae9c56c7f768c5a34568effa6b323035?s=47 ckochx
April 26, 2017

Rails to Phoenix

Rails to Phoenix
RailsConf 2016
April 26, 2017
by Christian Koch
@ckochx
ckoch@ncsasports.org

Ae9c56c7f768c5a34568effa6b323035?s=128

ckochx

April 26, 2017
Tweet

Transcript

  1. Rails to Phoenix How Elixir can level-you-up in Rails

  2. RailsConf 2017 Christian Koch ckoch@ncsasports.org NCSA Chicago, IL April 26,

    2017
  3. Agenda Introduce Elixir and the Phoenix Framework Define the relationship

    between Elixir, Erlang, and the OTP framework Compare a Phoenix Application to a Rails App
  4. What this talk is not No Phoenix Puns No Memes

    No Cat Pictures.
  5. None
  6. None
  7. None
  8. Why not Rails? Fast to Develop I already know it.

    It does so much for me. Sandi Metz just released 99 Bottles of OOP
  9. Phoenix Framework‡ • Server-side MVC pattern • Developed by Chris

    McCord and Dockyard • Aesthetically similar to Rails • Compiled • Phoenix ≠ Rails† • Phoenix programming is Elixir Programming ‡ http://www.phoenixframework.org/ † https://dockyard.com/blog/2015/11/18/phoenix-is-not-rails
  10. Functional Language Organized into Classes Class Inheritance Data and Methods

    bound to objects Objects as representations of “real” concepts ‡ Aspirational- Freer / Freeish Object Oriented Language Immutable Stateless Functions as first-class citizens Data Transformations Free‡ from side-effects
  11. Erlang Erlang: Erlang is a programming language used to build

    massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e- commerce, computer telephony and instant messaging. Erlang's runtime system has built- in support for concurrency, distribution and fault tolerance.‡ ‡ https://www.erlang.org/
  12. OTP OTP (Open Telecom Protocol): OTP is set of Erlang

    libraries and design principles providing middleware to develop these systems. It includes its own distributed database, applications to interface towards other languages, debugging and release handling tools.‡ ‡ https://www.erlang.org/
  13. Elixir Elixir: Elixir is a dynamic, functional language designed for

    building scalable and maintainable applications. Elixir leverages the Erlang Virtual Machine (VM), known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain.‡ ‡ http://elixir-lang.org/
  14. Why Phoenix? Functional (as in Lambda Calculus, not usable, although

    it is) Fast Asynchronous Explicit Scalable Pleasing Syntax (Similar to Ruby on Rails) -- Enjoyable to Write Built to Fail reliably
  15. http://www.phoenixframework.org/ http://www.techworld.com/apps/how-elixir-helped-bleacher-report-handle-8x-more-traffic- 3653957/

  16. class Example def self.transform(hash, key, second_arg) h = hash h[key]

    += second_arg h end end Ruby
  17. class Example def self.transform(hash, key, second_arg) … end end hash

    = {foo: "bar"} new_hash = Example.transform(hash, :foo, "second_arg") hash >> {:foo=>"barsecond_arg"} new_hash >> {:foo=>"barsecond_arg"}
  18. defmodule Example do def transform(map, key, second_arg) do Map.update!(map, :foo,

    fn(x) -> x <> second_arg end) end end Elixir
  19. defmodule Example do def transform(map, key, second_arg) do ... end

    end myMap = %{foo: "bar"} newMap = Example.transform(myMap, :foo, "second_arg") newMap >> %{foo: "barsecond_arg"} myMap >> %{foo: "bar"}
  20. defmodule Example do def transform(map, key, second_arg) do Map.update!(map, key,

    fn(x) -> x <> second_arg end) end end
  21. defmodule Example do def transform(map, second_arg) do Map.update!(map, key, fn(x)

    -> x <> second_arg end) end end
  22. defmodule Example do def transform(map, second_arg) do Map.update!(map, key, fn(x)

    -> x <> second_arg end) end end
  23. defmodule Example do def transform(map, second_arg) do Map.update!(map, key, fn(x)

    -> x <> second_arg end) end end
  24. class Example def self.transform(hash, key, second_arg) h = hash.dup h[:foo]

    += second_arg h end end hash = {foo: "bar"} new_hash = Example.transform(hash, :foo, "second_arg") hash >> {:foo=>"bar"} new_hash >> {:foo=>"barsecond_arg"}
  25. class Example def self.transform(hash, key, second_arg) hash.freeze hash[key] += second_arg

    hash end end hash = {foo: "bar"} Example.transform(hash, :foo, "second_arg") >> RuntimeError: can't modify frozen Hash
  26. class Example def self.transform(hash, key, second_arg) hash.freeze hash[key] << second_arg

    hash end end hash = {foo: []} Example.transform(hash, :foo, "second_arg") >> {foo: ["second_arg"]}
  27. Helpful things to know about Elixir No Objects, Elixir uses

    modules. Lists instead of Arrays and Maps instead of Hashes :atoms instead of :symbols BEAM (Erlang Virtual Machine) Tuples {:state_of_the_world, "The result of the function"} Use Recursion and Higher order functions like :map and :reduce Pattern Matching
  28. Basic Similarities between Rails and Phoenix Gemfile → mix.exs (Mix

    combines the features of Rake and Bundler) RubyGems (package manager) → Hex Bundle install → mix deps.get Generate Migrations → mix ecto.gen.migration add_table_tablename Run Migrations → mix ecto.migrate
  29. Code Generators Project Generator (check) mix phoenix.new railsConf --optional-flags Model,

    View, Controller generators (check) mix phoenix.gen.controller resource_name :action Scaffold Generator (check) mix phoenix.gen.html ModuleName ModuleNamePlural attribute: :type mix phoenix.gen.json ModuleName ModuleNamePlural attribute: :type
  30. Phoenix 1.2 (Current) Project Structure

  31. Phoenix 1.2 (Current) Project Structure

  32. Phoenix 1.3 (Future) Project Structure https://www.youtube.com/watch?v=tMO28ar0lW8 https://elixirforum.com/t/phoenix-v1-3-0-rc-0-released/3947

  33. Ecto:Phoenix ≈ ActiveRecord:Rails Database Ecto Adapter Dependency Ecto 2.0 compatible?

    PostgreSQL Ecto.Adapters.Postgres postgrex Yes MySQL Ecto.Adapters.MySQL mariaex Yes Mnesia EctoMnesia.Adapter ecto_mnesia Yes MSSQL MssqlEcto / Tds.Ecto mssql_ecto / tds_ecto Yes / No SQLite3 Sqlite.Ecto sqlite_ecto No MongoDB Mongo.Ecto mongodb_ecto No
  34. Phoenix is a Model View Controller (MVC) Framework

  35. Rails Controller Phoenix Controller defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def

    show(conn, %{"data" =>data}) do # do something render conn, "show.html", _variables end end module RailsConf class Athlete < ApplicationController def show # do something end end end
  36. Rails Controller Phoenix Controller module RailsConf class Athlete < ApplicationController

    def show # do something end end end defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def show(conn, %{"data" =>data}) do # do something render conn, "show.html", _variables end end
  37. For most conventional RESTful applications, the controller will receive the

    request (this is invisible to you as the developer)†, ...‡ ‡ http://guides.rubyonrails.org/action_controller_overview.html#what-does-a-controller-do-questionmark † Emphasis mine.
  38. defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def show(conn, %{"data" =>data}) do

    # do something render conn, "show.html", _variables end end
  39. defmodule RailsConf.Endpoint do use Phoenix.Endpoint, otp_app: :rails_conf socket "/socket", RailsConf.UserSocket

    plug Plug.Static, at: "/", from: :rails_conf, gzip: false, only: ~w(css fonts images js favicon.ico robots.txt) plug Plug.RequestId plug Plug.Logger plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Poison plug Plug.Session ...
  40. Plug:Phoenix ≈ Rack Middleware:Rails Rack Middleware in Rails is present

    and it does things. (But mostly that’s hidden) Plug in Phoenix is an explicit pipeline through with the connection (request lifecycle) flows. Plug: A module plug implements an init/1 function to initialize the options and a call/2 function which receives the connection and initialized options and returns the connection. Rack: A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.
  41. Rack Rails development.rb > Rails.application.configure do ... config.middleware.insert_before Rack::Head, Magical::Unicorns

    ... end
  42. Phoenix Controller defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def show(conn, %{"data"

    =>data}) do # do something render conn, "show.html", variables end end
  43. Phoenix Controller defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def show(conn, %{"data"

    =>data}) do # query the database render conn, "show.html", variables end end
  44. defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller alias RailsConf.Athlete def show(conn, %{"data"

    =>data}) do athlete = RailsConf.Repo.get!(Athlete, id) render(conn, "show.html", athlete: athlete) end end
  45. defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller alias RailsConf.Athlete def show(conn, %{"data"

    =>data}) do athlete=Repo.get_by(Athlete, name: data[“name”]) render(conn, "show.html", athlete: athlete) end end
  46. defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller alias RailsConf.Athlete def show(conn, %{"data"

    =>data}) do athlete = RailsConf.Repo.get!(Athlete, id) render(conn, "show.html", athlete: athlete) end end
  47. defmodule RailsConf.Admin.MyController use RailsConf.Web, :controller def show(conn, %{"data" =>data}) do

    # do some querying render conn, "special_show.html", _variables end end
  48. Phoenix is not your Application‡ ‡ Lance Halvorsen ElixirConf EU

    2016 https://www.youtube.com/watch?v=lDKCSheBc-8
  49. defmodule Example do def transform(map, second_arg) do Map.update!(map, :foo, fn(x)

    -> x <> second_arg end) end end Elixir
  50. The |> (pipe) operator Take the left-side and pass it

    as the first argument to the right-side. defmodule Example do def transform(map, key, second_arg) do map |> Map.update!(key, fn(x) -> x <> second_arg end) end end
  51. The |> (pipe) operator Take the left-side and pass it

    as the first argument to the right-side. defmodule Example do def transform(map, key, second_arg) do map |> Map.update!(key, fn(x) -> x <> second_arg end) end end
  52. The |> (pipe) operator Take the left-side and pass it

    as the first argument to the right-side. defmodule Example do def transform(map, key, second_arg) do map |> Map.update!(key, fn(x) -> x <> second_arg end) end end
  53. defmodule Example do def transform(map, key, second_arg) do map |>

    Map.update!(key, fn(x) -> x <> second_arg end) end end >> %{foo: "bar"} |> Example.transform(:foo, "second_arg") >> %{foo: "barsecond_arg"}
  54. “It’s a series of tubes.”‡ defmodule RailsConf.Presenters.Query do def present(terms)

    do %{} |> present_filters(terms) |> present_prefixes(terms) |> present_ranges(terms) |> present_query end end ‡ https://en.wikipedia.org/wiki/Series_of_tubes, https://en.wikiquote.org/wiki/Ted_Stevens
  55. Any web request can be viewed as a function Input:

    The request; a URL (function name) with one or more parameters Which sends the request through a series of transformations; and produces the output: As a user, I don’t care much more about it than that.
  56. defmodule RailsConf.Athlete do use Ecto.Schema use RailsConf.Web, :model @primary_key {:id,

    :binary_id, autogenerate: true} schema "athletes" do ... end end Phoenix Model
  57. defmodule RailsConf.Athlete do use Ecto.Schema import Ecto import Ecto.Changeset import

    Ecto.Query @primary_key {:id, :binary_id, autogenerate: true} schema "athletes" do...end end Phoenix Model
  58. defmodule RailsConf.Athlete do ... @primary_key {:id, :binary_id, autogenerate: true} schema

    "athletes" do field :name, :string field :position, :string ... end
  59. defmodule RailsConf.Athlete do ... @primary_key {:id, :binary_id, autogenerate: true} schema

    "athletes" do field :name, :string field :position, :string ... end
  60. defmodule RailsConf.Athlete do ... schema "athletes" do field :name, :string

    field :position, :string ... end def changeset(model, params \\ :empty) do … end
  61. defmodule RailsConf.Athlete do ... schema "athletes" do field :name, :string,

    default: “Harry” field :position, :string, null: false ... end def changeset(model, params \\ :empty) do … end
  62. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) |> update_change(:position, &String.downcase/1) ... end
  63. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) |> update_change(:position, &String.downcase/1) ... end
  64. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) |> update_change(:position, &String.downcase/1) ... end
  65. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) |> update_change(:position, &String.downcase/1) ... end
  66. defp validate_primary_position_id(changeset, value) do case Positions.valid_position_id?(value) do true -> changeset

    false -> add_error(changeset, :primary_position_id, "#{value} is not a valid primary_position_id") end end Phoenix Model Validation
  67. defp validate_primary_position_id(changeset, value) do case Positions.valid_position_id?(value) do true -> changeset

    false -> add_error(changeset, :primary_position_id, "#{value} is not a valid primary_position_id") end end Phoenix Model Validation
  68. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) … |> validate_primary_position_id(params[:position_id]) end
  69. defp validate_primary_position_id(changeset, value) do case Positions.valid_position_id?(value) do true -> changeset

    false -> add_error(changeset, :primary_position_id, "#{value} is not a valid primary_position_id") end end
  70. def create(conn, %{"athlete" => athlete_params}) do changeset = Athlete.changeset(%Athlete{}, athlete_params)

    case Repo.insert(changeset) do {:ok, _athlete} -> conn |> put_flash(:info, "Athlete created successfully.") |> redirect(to: athlete_path(conn, :index)) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end Phoenix Controller
  71. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> #handle the error end
  72. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> #handle the error end
  73. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> #handle the error end
  74. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> #Render an error view; Enumerate the errors end
  75. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> conn |> put_status(:unprocessable_entity) #422 |> put_flash(:error, "Errors when creating this athlete.") |> put_flash(:warn, "Please review the errors below.") |> render("new.html", changeset: changeset) end
  76. changeset = Athlete.changeset(%Athlete{}, athlete_params) case RailsConf.Repo.insert(changeset) do {:ok, _athlete} ->

    #do the happy path {:error, changeset} -> conn |> put_status(:unprocessable_entity) #422 |> put_flash(:error, "Errors when creating this athlete.") |> put_flash(:warn, "Please review the errors below.") |> render("new.html", changeset: changeset) end
  77. def create(conn, %{"athlete" => athlete_params}) do changeset = Athlete.changeset(%Athlete{}, athlete_params)

    case Repo.insert(changeset) do {:ok, _athlete} -> conn |> put_flash(:info, "Athlete created successfully.") |> redirect(to: athlete_path(conn, :index)) {:error, changeset} -> conn |> put_status(:unprocessable_entity) #422 |> put_flash(:warn, "Errors when creating this athlete.") |> put_flash(:error, "Please review the errors below.") |> render("new.html", changeset: changeset) end end
  78. None
  79. Complexity Aside Rich Hickey: Author of Clojure Great talk on

    Simplicity and Complexity vs. Easy https://www.infoq.com/presentations/Simple-Made-Easy
  80. Phoenix Views Views are Functions within Modules Views define decorators

    for the template files Views invoke the function Phoenix.View.render/3 In order to render the template files that contain the html
  81. From: web/templates/layout/app.html.eex > <!DOCTYPE html> <html lang="en"> <head></head> <body> <p

    class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> <main role="main"> <%= render @view_module, @view_template, assigns %> </main> </div> <!-- /container --> <script src="<%= static_path(@conn, "/js/app.js") %>"></script> </body> </html>
  82. <!DOCTYPE html> <html lang="en"> ... <main role="main"> <%= render @view_module,

    @view_template, assigns %> </main> ... </div> <!-- /container --> <script src="<%= static_path(@conn, "/js/app.js") %>"></script> </body> </html>
  83. defmodule RailsConf.AthleteView do use RailsConf.Web, :view end

  84. defmodule RailsConf.AthleteView do use RailsConf.Web, :view def title do "RailsConf

    -- ViewTitle" end end
  85. From: web/templates/athlete/show.html.eex > <h2>Show athlete</h2> <ul> <li><strong>Name:</strong> <%= @athlete.name %></li>

    <li><strong>Position:</strong> <%= @athlete.position %></li> <li><strong>Email:</strong> <%= @athlete.email %></li> </ul> <%= link "Edit", to: athlete_path(@conn, :edit, @athlete) %> <%= link "Back", to: athlete_path(@conn, :index) %>
  86. From: web/templates/athlete/show.html.eex > <h1><%= title %></h1> <h2>Show athlete</h2> <ul> <li><strong>Name:</strong>

    <%= @athlete.name %></li> <li><strong>Position:</strong> <%= @athlete.position %></li> <li><strong>Email:</strong> <%= @athlete.email %></li> </ul> <%= link "Edit", to: athlete_path(@conn, :edit, @athlete) %> <%= link "Back", to: athlete_path(@conn, :index) %>
  87. None
  88. It does share many similarities. Much more typing is required.

    More boilerplate code. Functional paradigm maps better to a web request. (According to me) Immutable and easier to reason about. Asynchronous Fast! Phoenix ≠ Rails
  89. But Elixir/Phoenix are too immature Fair Point Or is it?

    Elixir transpiles to BEAM bytecode‡ The BEAM is the BOMB! ‡ With intermediate stops in Erlang Abstract Format (EAF), Erlang Core, etc. https://elixirforum.com/t/getting-each-stage-of-elixirs-compilation-all-the-way-to-the-beam- bytecode/1873
  90. Should I use Phoenix? Yes! It Depends

  91. The Phoenix Framework It is bad when one thing becomes

    two. One should not look for anything else in the Way. It is the same for anything that is called a Way. If one understands things in this manner, he should be able to hear about all ways and be more and more in accord with his own.‡ ‡ Ghost Dog: The Way of the Samurai
  92. Thank you! Rails Conf 2017 Christian Koch ckoch@ncsasports.org NCSA Chicago,

    IL April 26, 2017
  93. None