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

Phoenix for Rails Developers

Phoenix for Rails Developers

Given at the Tokyo Rubyist Meetup on March 10th, 2016

Matthew Gillingham

March 10, 2016
Tweet

Other Decks in Programming

Transcript

  1. Comparisons • Elixir / Ruby • Plug / Rack •

    Ecto / ActiveRecord • Phoenix / Rails
  2. Elixir is similar to Ruby at a surface level but

    more similar to Erlang at a deeper level.
  3. Data types • integer • float • boolean • atom

    • string • list • tuple • map • struct
  4. Structs Maps with predefined keys, enforced by the compiler defmodule

    User do defstruct name: "John", age: 27 end %User{age: 29, name: "Paul"}
  5. Conditionals case {1, 2, 3} do {4, 5, 6} ->

    "This clause won't match" {1, x, 3} -> "This clause will match and bind x to 2 in this clause" _ -> "This clause would match any value" end
  6. Pattern Matching Equals is a match operator, not assignment iex>

    x = 2 2 iex> x = 1 1 iex> x 1 iex> 1 = x 1 iex> 2 = x ** (MatchError) no match of right hand side value: 1
  7. Pattern Matching Multiple Assignment iex> {a, b, c} = {:hello,

    "world", 42} {:hello, "world", 42} iex> a :hello iex> b "world" iex> c 42
  8. Pattern Matching Works with function parameters defmodule Math do def

    zero?(0), do: true def zero?(x) when is_number(x), do: false end
  9. Recursion defmodule Math do def sum_list([head|tail], accumulator) do sum_list(tail, head

    + accumulator) end def sum_list([], accumulator) do accumulator end end IO.puts Math.sum_list([1, 2, 3], 0) #=> 6
  10. Metaprogramming with macros defmodule Unless do def fun_unless(clause, expression) do

    if(!clause, do: expression) end defmacro macro_unless(clause, expression) do quote do if(!unquote(clause), do: unquote(expression)) end end end
  11. Everything is a function that takes a connection struct and

    returns a new connection struct. conn = put_resp_content_type(conn, "text/plain") conn = send_resp(conn, 200, "ok") conn
  12. It has the concept of a Repo. defmodule Sample.Repo do

    use Ecto.Repo, otp_app: :my_app end
  13. It defines models as structs. defmodule Sample.Weather do use Ecto.Schema

    schema "weather" do field :city # Defaults to type :string field :temp_lo, :integer field :temp_hi, :integer field :prcp, :float, default: 0.0 end end
  14. Query query = from w in Weather, where: w.precipiation >

    0 or is_nil(w.precipiation), select: w Repo.all(query) Weather |> where(city: "Kraków") |> order_by(:temp_lo) |> limit(10) |> Repo.all
  15. Changeset def changeset(user, params \\ :empty) do user |> cast(params,

    ~w(name email), ~w(age)) |> validate_format(:email, ~r/@/) |> validate_inclusion(:age, 18..100) |> unique_constraint(:email) end changeset = User.changeset(%User{}, %{age: 0, email: "[email protected]"}) {:error, changeset} = Repo.insert(changeset) changeset.errors #=> [age: "is invalid"]
  16. It has migrations. defmodule MyRepo.Migrations.CreatePosts do use Ecto.Migration def change

    do create table(:weather) do add :city, :string, size: 40 add :temp_lo, :integer add :temp_hi, :integer add :prcp, :float timestamps end end end
  17. Router defmodule MyApp.Router do use Phoenix.Router pipeline :browser do plug

    :fetch_session plug :accepts, ["html"] end scope "/" do pipe_through :browser get "/pages/:page", PageController, :show resources "/users", UserController, except: [:delete] end end
  18. Controller defmodule MyApp.UserController do use MyApp.Web, :controller plug :authenticate, usernames:

    ["jose", "eric", "sonny"] def show(conn, params) do user = Repo.get(User, id) render conn, "show.html", user: user end defp authenticate(conn, options) do if get_session(conn, :username) in options[:usernames] do conn else conn |> redirect(to: "/") |> halt() end end end
  19. Channels • Comes with support for WebSockets but can use

    other transports. • Has a socket struct which keeps state.
  20. Channels defmodule Project.UserSocket do use Phoenix.Socket channel "post:*", Project.PostChannel transport

    :websocket, Phoenix.Transports.WebSocket @max_age 2 * 7 * 24 * 60 * 60 def connect(%{"token" => token}, socket) do case Phoenix.Token.verify(socket, "user socket", token, max_age: @max_age) do {:ok, user_id} -> user = Repo.get!(Project.User, user_id) {:ok, assign(socket, :current_user, user)} {:error, _reason} -> :error end end def connect(_params, _socket), do: :error def id(socket), do: "users_socket:#{socket.assigns.current_user.id}" end end
  21. Channels def join("posts:" <> post_id, _params, socket) do post =

    Repo.get!(Project.Post, post_id) comments = Repo.all( from c in assoc(post, :comments), order_by: [desc: c.at, asc: c.id], limit: 200, preload: [:user] ) resp = %{comments: Phoenix.View.render_many(comments, CommentView, "comment.json")} {:ok, resp, assign(socket, :post_id, post.id)} end
  22. Channels def handle_in("new_comment", params, user, socket) do changeset = user

    |> build(:comments, post_id: socket.assigns.post_id) |> Project.Comment.changeset(params) case Repo.insert(changeset) do {:ok, ann} -> broadcast_comment(socket, ann) {:reply, :ok, socket} {:error, changeset} -> {:reply, {:error, %{errors: changeset}}, socket} end end defp broadcast_comment(socket, comment) do comment = Repo.preload(comment, :user) rendered_comment = Phoenix.View.render(CommentView, "comment.json", %{ comment: comment }) broadcast! socket, "new_comment", rendered_comment end
  23. Supervisors defmodule Project.Supervisor do use Supervisor def start_link() do Supervisor.start_link(__MODULE__,

    [], name: __MODULE__) end def init(_opts) do children = [ worker(MyApp, [], restart: :temporary) ] supervise children, strategy: :simple_one_for_one end end
  24. Most code samples were taken from • Documentation at elixir-lang.org

    • https://hexdocs.pm/ecto/Ecto.html • Programming Phoenix by Chris McCord, Bruce Tate, and José Valim.