验证错误未显示在 Phoenix LiveView 表单中

Validation errors not shown in Phoenix LiveView form

提问人:Greeneco 提问时间:11/2/2023 更新时间:11/4/2023 访问量:50

问:

我正在将 Elixir 与 Phoenix 一起使用。我有一个带有文本输入的表单,它只允许最多 255 个字符的文本。但是,错误没有显示,我无法弄清楚为什么它没有显示错误。唯一的问题是,当数据库或ecto尝试插入它并显示错误闪烁消息时,它将拒绝它。但我预计验证步骤会显示错误消息。

.输入.modal、...是默认的核心组件。

我还注意到assigns.wasm_form.source.errors不为空,但assigns.wasm_for.errors为空。

我的相关实时取景代码:

defmodule BotLeagueBackendWeb.ProfileLive do
  use BotLeagueBackendWeb, :live_view

  alias BotLeagueBackend.Wasm
  alias BotLeagueBackend.WasmService

  @impl true
  def mount(_params, session, socket) do
    user_id = session["id"]

    socket = socket
     |> assign(:conn, %{request_path: "/profile", assigns: %{user: user_id}})

    if connected?(socket) do
      wasm_form =
        %Wasm{}
        |> Wasm.changeset(%{})
        |> to_form(as: "wasm_form")

      uploaded_wasm_modules = WasmService.get_wasm_modules_for_user(user_id)

      socket =
        socket
        |> assign(:wasm_form, wasm_form)
        |> allow_upload(:wasm_module, max_entries: 1, max_file_size: 16_000_000, accept: ~w(.wasm))
        |> stream(:uploaded_wasm_modules, uploaded_wasm_modules)
      {:ok, socket}
    else
      {:ok, assign(socket, loading: true)}
    end
  end

  @impl true
  def render(%{loading: true} = assigns) do
    ~H"""
      <p>Loading...</p>
    """
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.button type="button" phx-click={show_modal("upload-wasm-module-modal")}>
      Upload New Bot Version
    </.button>

    <.modal id="upload-wasm-module-modal">
      <.simple_form for={@wasm_form} phx-change="validate" phx-submit="upload" phx-feedback-for="wasm_form">
        <%= for entry <- @uploads.wasm_module.entries do %>
          <%= for err <- upload_errors(@uploads.wasm_module, entry) do %>
            <p class="text-base text-red-500">
              <%= error_to_string(err) %>
            </p>
          <% end %>
        <% end %>
        <.live_file_input upload={@uploads.wasm_module} required />
        <.input
          field={@wasm_form[:name]}
          type="text"
          label="Enter a name for your bot's current version"
          required
        />
        <.button type="submit" phx-disable-with="Saving...">Upload</.button>
      </.simple_form>
    </.modal>

    <div class="mt-8 max-w-screen-md">
      <h4 class="font-semibold mb-4">Uploaded Bot Versions</h4>
      <table class="min-w-full">
      <thead>
        <tr>
          <th class="px-6 py-3 border-b-2 border-gray-300 text-left uppercase">Name</th>
          <th class="px-6 py-3 border-b-2 border-gray-300 text-left uppercase">Active</th>
          <th class="px-6 py-3 border-b-2 border-gray-300 text-left uppercase">MD5</th>
        </tr>
      </thead>
      <tbody id="uploaded_wasm_modules_stream" phx-update="stream">
          <tr :for={{dom_id, uploaded_wasm_module} <- @streams.uploaded_wasm_modules} id={dom_id} class="border-t border-gray-300">
            <td class="px-6 py-4 whitespace-no-wrap"><%= uploaded_wasm_module.name %></td>
            <td class="px-6 py-4 whitespace-no-wrap"><%= uploaded_wasm_module.active %></td>
            <td class="px-6 py-4 whitespace-no-wrap"><%= uploaded_wasm_module.md5_hash %></td>
          </tr>
      </tbody>
      </table>
    </div>
    """
  end

  @impl true
  def handle_event("upload", %{"wasm_form" => wasm_params}, socket) do
    %{user: user_id} = socket.assigns.conn.assigns

    # TODO consume_file and saving it should be done in WasmService

    {file_path, md5_hash} = List.first(consume_file(socket))

    case WasmService.add_wasm_module_for_user(
           file_path: file_path,
           md5_hash: md5_hash,
           name: wasm_params["name"],
           user_id: user_id
         ) do
      {:ok, _wasm} ->
        socket =
          socket
          |> put_flash(:info, "Wasm module uploaded successfully")
          |> push_navigate(to: ~p"/profile")

        {:noreply, socket}

      {:error, %Ecto.Changeset{} = changeset} ->
        socket =
          socket
          |> put_flash(:error, "Some error occurred")
          |> push_navigate(to: ~p"/profile")

        {:noreply, socket}
    end
  end

  @impl true
  def handle_event("validate", %{"wasm_form" => params}, socket) do
    wasm_form =
      %Wasm{}
      |> Wasm.changeset(params)
      |> to_form(as: "wasm_form")

    {:noreply, assign(socket, wasm_form: wasm_form)}
  end

  defp consume_file(socket) do
    consume_uploaded_entries(socket, :wasm_module, fn %{path: path}, _entry ->
      file_id = Ecto.UUID.generate()

      dest =
        Path.join([
          :code.priv_dir(:bot_league_backend),
          "static",
          "uploads",
          file_id <> ".wasm"
        ])

      File.cp!(path, dest)

      md5_hash = File.stream!(dest,[],2048)
        |> Enum.reduce(:crypto.hash_init(:md5),fn(line, acc) -> :crypto.hash_update(acc,line) end )
        |> :crypto.hash_final
        |> Base.encode16

      {:ok, {~p"/uploads/#{Path.basename(dest)}", md5_hash}}
    end)
  end

  def error_to_string(:too_large), do: "Too large"
  def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
  def error_to_string(:too_many_files), do: "You have selected too many files"
end

和我的领域模型

defmodule BotLeagueBackend.Wasm do
  use BotLeagueBackend, :domain_model

  alias BotLeagueBackend.User

  @primary_key {:id, Ecto.UUID, default: Ecto.UUID.generate()}

  schema "wasms" do
    field :file_path, :string
    field :md5_hash, :string
    field :name, :string
    field :active, :boolean, default: true
    belongs_to :user, User, foreign_key: :user_id, type: :binary_id

    timestamps()
  end

  def changeset(wasm, params \\ %{}) do
    wasm
    |> cast(params, [:file_path, :md5_hash, :name, :active, :user_id])
    |> validate_required([:file_path, :md5_hash, :name, :active, :user_id])
    |> validate_length(:file_path, max: 255, min: 1, message: "must be between 1 and 255 characters")
    |> validate_length(:md5_hash, max: 32, min: 32, message: "invalid md5 hash")
    |> validate_length(:name, max: 255, min: 1, message: "must be between 1 and 255 characters")
  end

  def new(
        file_path: file_path,
        md5_hash: md5_hash,
        name: name,
        active: active,
        user_id: user_id
      )
      when is_binary(file_path) and is_binary(md5_hash) and is_binary(name) and is_binary(user_id) do
    {:ok, user_id} = Ecto.UUID.cast(user_id)

    wasm = %@self{
      file_path: file_path,
      md5_hash: md5_hash,
      name: name,
      active: active,
      user_id: user_id
    }

    {:ok, wasm}
  end
end

我在这里犯了什么错误。我的目标是,如果我输入的文本超过 255 个字符,则会显示一条错误消息。

Elixir Ecto Phoenix-Live-View

评论


答:

1赞 Peaceful James 11/4/2023 #1

我没有完全检查您的代码,但根据我的经验,这通常发生在变更集没有密钥集时。:action

您可以像 一样简单地执行此操作。Map.put(changeset, :action, :validate)

当您或使用变更集时,这些功能将自动放在变更集上或变更集上。Repo.insertRepo.update:action:insert:update

我要尝试的第一件事就是改变这一点

  @impl true
  def handle_event("validate", %{"wasm_form" => params}, socket) do
    wasm_form =
      %Wasm{}
      |> Wasm.changeset(params)
      |> to_form(as: "wasm_form")

进入这个

  @impl true
  def handle_event("validate", %{"wasm_form" => params}, socket) do
    wasm_form =
      %Wasm{}
      |> Wasm.changeset(params)
      |> Map.put(:action, :validate)
      |> to_form(as: "wasm_form")