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

Um clone do Twitter com Phoenix e Vue.js

Um clone do Twitter com Phoenix e Vue.js

Slides da palestra que apresentei na Ruby Conf Brasil de 2016 (e depois no ElugSP #7), onde mostro como criar um MVP do Twitter com Phoenix e Vue.js.
O código-fonte do projeto está em https://github.com/philss/sabiah

Philip Sampaio

September 23, 2016
Tweet

More Decks by Philip Sampaio

Other Decks in Programming

Transcript

  1. scope "/", Sabiah do pipe_through :browser # Use the default

    browser stack get "/", TimelineController, :index end web/router.ex
  2. defmodule Sabiah.TimelineController do use Sabiah.Web, :controller def index(conn, _params) do

    render conn, "index.html" end end web/controllers/timeline_controller.ex
  3. defmodule Sabiah.TimelineController do use Sabiah.Web, :controller def index(conn, _params) do

    render conn, "index.html" end end web/controllers/timeline_controller.ex
  4. <h2>Timeline</h2> <div id='timeline-app'> <input type='text' v-model='newTweet' placeholder='What is happening?'> <button

    @click='postTweet' class='button'>Tweet</button> <ul class='timeline'> <li class='tweet' v-for='tweet in tweets'> <span class='tweet__content'>{{ tweet }}</span> </li> </ul> </div> web/templates/timeline/index.html.eex
  5. <!doctype html> <html> <body> <div id='app'> <input type='text' v-model='message'> {{

    message }} </div> <script src=‘https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.7/vue.min.js'></script> <script> new Vue({ el: '#app', data: { message: 'Hello world' } }); </script> </body> </html> priv/examples/vue.html
  6. <h2>Timeline</h2> <div id='timeline-app'> <input type='text' v-model='newTweet' placeholder='What is happening?'> <button

    @click='postTweet' class='button'>Tweet</button> <ul class='timeline'> <li class='tweet' v-for='tweet in tweets'> <span class='tweet__content'>{{ tweet }}</span> </li> </ul> </div> web/templates/timeline/index.html.eex
  7. import 'phoenix_html'; import Vue from 'vue/dist/vue'; new Vue({ el: '#timeline-app',

    data: { newTweet: '', tweets: [] }, methods: { postTweet: function() { this.tweets.unshift(this.newTweet); this.newTweet = ''; } } }); web/static/js/app.js
  8. <h2>Timeline</h2> <div id='timeline-app'> <input type='text' v-model='newTweet' placeholder='What is happening?'> <button

    @click='postTweet' class='button'>Tweet</button> <ul class='timeline'> <li class='tweet' v-for='tweet in tweets'> <span class='tweet__content'>{{ tweet.content }}</span> </li> </ul> </div> web/templates/timeline/index.html.eex
  9. import socket from './socket'; const userId = 42; let channel

    = socket.channel(`timeline:${userId}`, {}); channel.join() .receive('ok', resp => { console.log('Joined successfully', resp) }) .receive('error', resp => { console.log('Unable to join', resp) }); // The app ... web/static/js/app.js
  10. defmodule Sabiah.TimelineChannel do use Sabiah.Web, :channel def join("timeline:", _payload, _socket)

    do {:error, %{reason: "User id is missing."}} end def join("timeline:" <> user_id, _payload, socket) do socket = assign(socket, :user_id, user_id) {:ok, socket} end def handle_in("new_tweet", payload, socket) do broadcast socket, "new_tweet", payload {:noreply, socket} end end web/channels/timeline_channel.ex
  11. defmodule Sabiah.TimelineChannel do use Sabiah.Web, :channel def join("timeline:", _payload, _socket)

    do {:error, %{reason: "User id is missing."}} end def join("timeline:" <> user_id, _payload, socket) do socket = assign(socket, :user_id, user_id) {:ok, socket} end def handle_in("new_tweet", payload, socket) do broadcast socket, "new_tweet", payload {:noreply, socket} end end web/channels/timeline_channel.ex
  12. let app = new Vue({ el: '#timeline-app', data: { newTweet:

    '', tweets: [] }, methods: { postTweet: function() { channel.push('new_tweet', { tweet: this.newTweet }); this.newTweet = ''; } } }); channel.on('new_tweet', (payload) => { app.tweets.unshift(payload.tweet); }); web/static/js/app.js
  13. defmodule Sabiah.TimelineChannel do use Sabiah.Web, :channel def join("timeline:", _payload, _socket)

    do {:error, %{reason: "User id is missing."}} end def join("timeline:" <> user_id, _payload, socket) do socket = assign(socket, :user_id, user_id) {:ok, socket} end def handle_in("new_tweet", payload, socket) do broadcast socket, "new_tweet", payload {:noreply, socket} end end web/channels/timeline_channel.ex
  14. let app = new Vue({ el: '#timeline-app', data: { newTweet:

    '', tweets: [] }, methods: { postTweet: function() { channel.push('new_tweet', { tweet: this.newTweet }); this.newTweet = ''; } } }); channel.on('new_tweet', (payload) => { app.tweets.unshift(payload.tweet); }); web/static/js/app.js
  15. defmodule Sabiah.Tweet do use Sabiah.Web, :model schema "tweets" do field

    :content, :string belongs_to :user, Sabiah.User timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:user_id, :content]) |> validate_required([:user_id, :content]) end end web/models/tweet.ex
  16. alias Sabiah.Tweet def handle_in("new_tweet", %{ "content" => content } =

    payload, socket) do user_id = socket.assigns[:user_id] tweet_changeset = Tweet.changeset(%Tweet{}, %{user_id: user_id, content: content}) Repo.insert!(tweet_changeset) broadcast socket, "new_tweet", payload {:noreply, socket} end web/channels/timeline_channel.ex
  17. alias Sabiah.Tweet def handle_in("new_tweet", %{ "content" => content } =

    payload, socket) do user_id = socket.assigns[:user_id] tweet_changeset = Tweet.changeset(%Tweet{}, %{user_id: user_id, content: content}) Repo.insert!(tweet_changeset) broadcast socket, "new_tweet", payload {:noreply, socket} end web/channels/timeline_channel.ex
  18. def changeset(struct, params \\ %{}) do struct |> cast(params, [:user_id,

    :content]) |> validate_required([:user_id, :content]) end web/models/tweet.ex
  19. <%= for user <- @users do %> <tr> <td><%= user.name

    %></td> <td><%= user.username %></td> <td> <%= form_for follower_changeset(user.id),
 follower_path(@conn, :create), fn f -> %> <%= hidden_input f, :followed_user_id, value: user.id %> <%= submit "+ follow", class: "button" %> <% end %> </td> </tr> <% end %> web/templates/user/index.html.eex
  20. defmodule Sabiah.UserView do use Sabiah.Web, :view alias Sabiah.Follower def follower_changeset(user_id)

    do Follower.changeset(%Follower{followed_user_id: user_id}) end end web/views/user_view.ex
  21. def create(conn, %{"follower" => params}) do user_id = get_session(conn, :user_id)

    params = Map.put(params, "user_id", user_id) changeset = Follower.changeset(%Follower{}, params) case Repo.insert(changeset) do {:ok, follow} -> conn |> put_flash(:info, "You are now following #{follow.followed_user_id}") |> redirect(to: user_path(conn, :index)) {:error, changeset} -> IO.inspect(changeset) conn |> put_flash(:error, "You seems to be logged out. Create an user to continue.") |> redirect(to: user_path(conn, :new)) end end web/views/user_view.ex
  22. &

  23. sabiah ⏳ 1. tweetar ✅ 2. seguir ✅ 3. ver

    timeline com tweets de quem sigo
  24. def handle_in("new_tweet", %{"content" => content}, socket) do user_id = socket.assigns[:user_id]

    tweet = save_tweet!(user_id, content) response_payload = decorate_payload(user_id, %{"content" => content}) broadcast socket, "new_tweet", response_payload TweetBroadcaster.broadcast_to_followers(tweet, response_payload) {:noreply, socket} end web/channels/timeline_channel.ex
  25. def handle_in("new_tweet", %{"content" => content}, socket) do user_id = socket.assigns[:user_id]

    tweet = save_tweet!(user_id, content) response_payload = decorate_payload(user_id, %{"content" => content}) broadcast socket, "new_tweet", response_payload TweetBroadcaster.broadcast_to_followers(tweet, response_payload) {:noreply, socket} end web/channels/timeline_channel.ex
  26. defmodule Sabiah.TweetBroadcaster do alias Sabiah.{Endpoint, Repo} import Ecto.Query def broadcast_to_followers(tweet,

    payload) do followers_ids_query = from f in "followers", where: f.followed_user_id == ^tweet.user_id, select: f.user_id followers_ids = Repo.all(followers_ids_query) Enum.map(followers_ids, fn(user_id) -> Endpoint.broadcast!("timeline:#{user_id}", "new_tweet", payload) end) end end web/channels/tweet_broadcaster.ex
  27. sabiah ⏳ 1. tweetar ✅ 2. seguir ✅ 3. ver

    timeline com tweets de quem sigo