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

Rails to Phoenix

ckochx
April 26, 2017

Rails to Phoenix

Rails to Phoenix
RailsConf 2016
April 26, 2017
by Christian Koch
@ckochx
[email protected]

ckochx

April 26, 2017
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. Why not Rails? Fast to Develop I already know it.

    It does so much for me. Sandi Metz just released 99 Bottles of OOP
  3. 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
  4. 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
  5. 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/
  6. 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/
  7. 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/
  8. 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
  9. 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"}
  10. 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"}
  11. 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"}
  12. 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
  13. 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"]}
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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.
  21. 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 ...
  22. 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.
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. Phoenix is not your Application‡ ‡ Lance Halvorsen ElixirConf EU

    2016 https://www.youtube.com/watch?v=lDKCSheBc-8
  29. 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
  30. 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
  31. 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
  32. 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"}
  33. “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
  34. 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.
  35. 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
  36. 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
  37. defmodule RailsConf.Athlete do ... @primary_key {:id, :binary_id, autogenerate: true} schema

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

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

    field :position, :string ... end def changeset(model, params \\ :empty) do … end
  40. 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
  41. ... def changeset(model, params \\ :empty) do model |> cast(params)

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

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

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

    |> validate_required(required_fields) |> update_change(:position, &String.downcase/1) ... end
  45. 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
  46. 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
  47. ... def changeset(model, params \\ :empty) do model |> cast(params)

    |> validate_required(required_fields) … |> validate_primary_position_id(params[:position_id]) end
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. Complexity Aside Rich Hickey: Author of Clojure Great talk on

    Simplicity and Complexity vs. Easy https://www.infoq.com/presentations/Simple-Made-Easy
  55. 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
  56. 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>
  57. <!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>
  58. 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) %>
  59. 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) %>
  60. 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
  61. 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
  62. 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