3

In liveview, how can I pass the user data from leex to the context? I have phx.gen.live a profiles context, and I want to add user_id to the profile every time user create the new profile. I change the create_profile code to:

**profiles.ex (context)**
  def create_profile(attrs \\ %{}, userid) do
    attrs = Map.put(attrs, "user_id", userid)
    %Profile{}
    |> Profile.changeset(attrs)
    |> Repo.insert()
  end

I am using pow, so in normal phoenix case, I would just do this:

user = Pow.Plug.current_user(conn) #<-- this is conn
Profiles.create_profile(profile_params, user.id)

but in liveview, instead of conn, it use socket. So I am not sure how to go about it.

sooon
  • 4,718
  • 8
  • 63
  • 116
  • 3
    you can create a plug and pass the user_id through session – Daniel Feb 16 '21 at 02:05
  • IDK if it answers your question but have a look at https://stackoverflow.com/questions/65357773/how-to-pass-plug-loaded-data-to-liveview-components/65359208#65359208 And if you are using standalone components inside any of your controllers you can pass it as session as well https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#live_render/3 – Evaldo Bratti Feb 17 '21 at 11:36
  • Thanks for the info. After spending the whole day staring at the code, I finally understand how it works. Anyway, I will read the link when I have time later. – sooon Feb 17 '21 at 11:47

3 Answers3

3

There are lots of different ways to do this but I will describe a straightforward approach.

  1. In the function where you "log in" a user, I assume you have access to the conn. I also assume you have a user_token, if you are using Pow. If so, do this:
conn
|> put_session(:user_token, user_token)
  1. Now go to your live_helpers.ex file (or create it if you don't have one) and make a function like this:
  def assign_defaults(session, socket) do
    socket =
      socket
      |> assign_new(:current_user, fn ->
        find_current_user(session)
      end)

    socket
  end
  1. Also, in live_helpers, write this function:
  defp find_current_user(session) do
    with user_token when not is_nil(user_token) <- session["user_token"],
         %User{} = user <- Accounts.get_user_by_session_token(user_token),
         do: user
  end
  1. Now in any LiveView modules where you need to access the current_user, just put this in your mount function:
  def mount(_params, session, socket) do
    socket =
      assign_defaults(session, socket)

    {:ok, socket}
  end

And then you will have access to the session.assigns.current_user.

UPDATE: since I wrote my answer, a new function now exists that is well-suited for such "default assigns": https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#on_mount/1

These days, I will make a module called DefaultAssigns and use this function. It's a small difference but it's certainly neater.

Peaceful James
  • 1,807
  • 1
  • 7
  • 16
0

Here's a simple pattern pulled from https://github.com/fly-apps/live_beats. It has the following steps:

  1. Add current_user to socket assigns.
  2. Use the on_mount in the live views where you want it.
  3. Access current_user in the template.

Add current_user to socket assigns in on_mount

(see lib/live_beats_web/controllers/user_auth.ex):

defmodule LiveBeatsWeb.UserAuth do
  import Plug.Conn
  import Phoenix.Controller

  alias Phoenix.LiveView
  alias LiveBeats.Accounts
  alias LiveBeatsWeb.Router.Helpers, as: Routes

  def on_mount(:current_user, _params, session, socket) do
    case session do
      # I think you have to manually add `user_id` to session. I used
`user_token` instead of `user_id` (because it's already in session due to the `fetch_current_user` plug in the browser pipeline) and `Accounts.get_user_by_session_token(user_token)` instead of `Accounts.get_user(user_id)`
      %{"user_id" => user_id} ->
        {:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user(user_id) end)}

      %{} ->
        {:cont, LiveView.assign(socket, :current_user, nil)}
    end
  end

...
end

Use the on_mount in the live views where you want it.

(see lib/live_beats_web/live/player_live.ex):

defmodule LiveBeatsWeb.PlayerLive do
  use LiveBeatsWeb, {:live_view, container: {:div, []}}

  alias LiveBeats.{Accounts, MediaLibrary}
  alias LiveBeats.MediaLibrary.Song

  # this uses the `on_mount` defined above. `:current_user` matches the first argument of `on_mount` above. It can say whatever you want, but it has to match in both places.
  on_mount {LiveBeatsWeb.UserAuth, :current_user}
  
  ...

  def mount(_parmas, _session, socket) do
    %{current_user: current_user} = socket.assigns

    if connected?(socket) do
      Accounts.subscribe(current_user.id)
    end

    socket =
      socket
      |> assign(
        song: nil,
        playing: false,
        profile: nil,
        current_user_id: current_user.id,
        own_profile?: false
      )
      |> switch_profile(current_user.active_profile_user_id || current_user.id)

    {:ok, socket, layout: false, temporary_assigns: []}
  end

Access current_user in the template

(I didn't find an example of this in that repo, but here's how you get properties of current_user)

User id: <%= @current_user.id %>
salsbury
  • 2,777
  • 1
  • 19
  • 22
  • Here is a longer article with other similar patterns you can use: https://blog.appsignal.com/2022/01/25/securing-your-phoenix-liveview-apps.html – salsbury Mar 10 '22 at 04:56
0
  1. Declares a module callback to be invoked on the LiveView's mount at router.
live_session :authenticated, on_mount: {AppWeb.InitAssigns, :current_user} do
    scope "/", AppWeb do
      pipe_through([:browser, :require_authenticated_user])

      live("/users/dashboardb", DashboardLive.Index, :index)
end
end
  1. Create InitAssigns:
defmodule AppWeb.InitAssigns do
  @moduledoc """
  Ensures common `assigns` are applied to all LiveViews attaching this hook.
  """
  import Phoenix.LiveView
  import Phoenix.Component

  alias App.Accounts
  alias App.Accounts.User

  def on_mount(:current_user, params, session, socket) do
    IO.inspect(params, label: "+++++++++++++++=========================I WAS HIT")
    # code
    socket =
      socket
      |> assign_new(:current_user, fn ->
        find_current_user(session)
      end)

    if socket.assigns.current_user do
      {:cont, socket}
    else
      {:halt, redirect(socket, to: "/")}
    end
  end

  defp find_current_user(session) do
    with user_token when not is_nil(user_token) <- session["user_token"],
         %User{} = user <- Accounts.get_user_by_session_token(user_token),
         do: user
  end
end
  1. Done
Peter Achieng
  • 21
  • 1
  • 4