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

Phoenix for Rails Developers

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Phoenix for Rails Developers

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

Avatar for Matthew Gillingham

Matthew Gillingham

March 10, 2016

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.