Can't find LiveView controller to associate user with entry

I’ve used the liveview generator for a database table that will hold mandarin characters and their translation.

I can enter a character and save it to the database, but I can’t find a way to save the user_id to the table; and with liveview not using the controller concept in the same way (correct?) I can’t figure out where to make this association.

I don’t want to put it as a user facing field even though it isn’t a high stakes situation because I’d like to be able to use this same code to build more secure things later. I’ve tried to trace the logic through the code and this is what I am getting:

Start at router.ex with /mandarin_characters/new

live "/mandarin_characters/new", MandarinCharacterLive.Index, :new

This takes me to live/mandarin_character_live/index.ex with the :new action, but I don’t want to put the id in assign because it will be visible to the user right?

  defp apply_action(socket, :new, _params) do
    |> assign(:page_title, "New Mandarin Character")
    |> assign(:mandarin_character, %MandarinCharacter{})
  end

The assign appears to reference the mandarin_character struct in my Languages context /languages/mandarin_character.ex and I tried to put the user_id in the changeset but it seems like I need to have inserted it into…something…before it comes here?

schema "mandarin_characters" do
    field :etymology, :string
    field :final, :string
    field :hanzi_standard, :string
    field :hanzi_traditional, :string
    field :image, :binary
    field :initial, :string
    field :meaning, :string
    field :mnemonic, :string
    field :part_of_speech, :string
    field :pinyin, :string
    field :pronounciation_helper, :string
    field :radical, :string
    field :sound, :binary
    field :stroke_order_simplified, :binary
    field :stroke_order_traditional, :binary
    field :tone, :string
    field :translated_to, :string
    field :vote_value, :integer

    timestamps()

    belongs_to :user, Users
  end

  @doc false
  def changeset(mandarin_character, attrs) do
    mandarin_character
    |> cast(attrs, [:hanzi_standard, :hanzi_traditional, :pinyin, :meaning, :pronounciation_helper, :mnemonic, :part_of_speech, :radical, :initial, :final, :tone, :etymology, :image, :sound, :stroke_order_simplified, :stroke_order_traditional, :translated_to, :user_id, :vote_value])
    |> validate_required([:hanzi_standard])
  end

this is the user struct that it is referencing

schema "user" do
    field :email, :string
    field :password, :string, virtual: true
    field :hashed_password, :string
    field :confirmed_at, :naive_datetime

    timestamps()

    has_many :mandarin_characters, MandarinCharacter
  end

I can’t see where the conn would pass in through this process, normally I would look for the controller, but that idea seems to be different for liveview.

I am thinking that I could do something in the languages/languages.ex file?

  def create_mandarin_character(attrs \\ %{}) do

    %MandarinCharacter{}
    |> MandarinCharacter.changeset(attrs)
    |> Repo.insert()
  end

but I can’t see how the conn struct would pass through here either…

Thanks for any help

Hello!

So in your router pipeline you must have some code (a plug) that assigns the user_id in your session. Something in the lines of put_session(:user_id, current_user.id). This is also what you usually do when using controllers.

The LiveView mount/3 callback receives that session. So you can get the user_id from it and assign to the socket to use later:

  def mount(params, %{"user_id" => user_id}, socket) do
    {:ok, assign(socket, user_id: user_id)}
  end

And whenever you need it you can do socket.assigns.user_id!

Thank you so much, this has gotten me much further than I was, but I have one question about the assign function because it won’t let me add more than one keyword it seems…

I have found where the pipeline that you mentioned is by looking for the put_session code, and I found it in the users_auth section and added the put_session(:user_id, current_user.id) like you mentioned, but calling it what it was listed here as “users”:

  def login_users(conn, users, params \\ %{}) do
    token = Accounts.generate_users_session_token(users)
    users_return_to = get_session(conn, :users_return_to)

    conn
...
    |> put_session(:users_id, users.id)
...
  end

I then went to the mount command and grabbed the users_id from the sessions argument:

  def mount(_params, %{"users_id" => users_id}, socket) do
    {:ok, assign(socket, users_id: users_id)}
    {:ok, assign(socket, :mandarin_characters, list_mandarin_characters())}
  end

I can inspect and see that I am getting the correct users_id here, but it doesn’t seem to be accepting it permanently as I assume the next assign statement is overwriting it?

But when I try to include it in the same assign function like it mentions in the liveview docs

assign(socket, key, value)

View Source

Adds key value pairs to socket assigns.

A single key value pair may be passed, or a keyword list of assigns may be provided to be merged into existing socket assigns.

Examples

iex> assign(socket, :name, "Elixir")
iex> assign(socket, name: "Elixir", logo: "💧")

This is looks the same idea to me:

  def mount(_params, %{"users_id" => users_id}, socket) do
    {:ok, assign(socket, :mandarin_characters, users_id: users_id, list_mandarin_characters())}
  end

But I get a syntax error:

SyntaxError

lib/walking_mandarin_web/live/mandarin_character_live/index.ex:11: syntax error before: list_mandarin_characters

Thank you for your help, I’m sure there is an incredibly simple solution that I am just not seeing.

I just answered my own question, I was reading the documents wrong! The function was the value for the keyword mandarin_characers, so I had to write it like this:

  def mount(_params, %{"users_id" => users_id}, socket) do
    {:ok, assign(socket, mandarin_characters: list_mandarin_characters(), users_id: users_id)}
  end

Thank you again for your help!

2 Likes