渲染phoenix视图时“(Protocol.UndefinedError)协议Enumerable未实现”

问题描述 投票:0回答:1

对于 Elixir、Phoenix/Ecto 和 Erlang 来说,总体来说是新手,所以请耐心等待。

我正在使用 Ecto 在 Phoenix 中定义模型、视图和控制器的其他工作示例,但我只是不明白为什么他们的版本可以工作,而我的版本却不能。 我正在尝试使用 TDD 来引导我进入一个工作 API(对于该服务来说是全新的),并且继续遇到这个 Enumerable 问题。

模型(对话)直接取自 Exto 模式和变更集,并使用 Phoenix 的工具将视图和模型链接在一起传递到视图。 虽然此时我没有单独的服务类来隐藏 Ecto 集成,但代码在其他方面似乎几乎与我们系统中其他工作控制器完全相同。那我错过了什么?

这是我的代码段: 路由器代码


      scope "/conversations" do
        post "/", ConversationController, :create
      end

控制器

defmodule ThingyWeb.ConversationController do
  use ThingyWeb, :controller

  alias Thingy.Conversations.Conversation
  alias Thingy.Repo

  def create(conn, conversation_params) do
    case Thingy.Auth.validate_session(conn) do
      {:ok, _} ->
        result =
          %Conversation{}
          |> Conversation.changeset(conversation_params)
          |> Repo.insert()

        case result do
          {:ok, conversation} ->
            IO.puts("store to db went well, now trying to render response")
            IO.inspect(conversation)
            conn
            |> put_status(201)
            |> render("created.json", conversation)

          {:error, _} ->
            conn
            |> put_status(400)
            |> render("error.json", %{message: "Unable to process conversation as provided"})
        end

      {:error, "No token"} ->
        conn
        |> put_status(401)
        |> render("error.json", %{message: "Authentication failed (no token)"})

      {:error, "Not found"} ->
        conn
        |> put_status(404)
        |> render("error.json", %{message: "Authentication failed (not found)"})
    end
  end
end

型号

defmodule Thingy.Conversations.Conversation do
  use Ecto.Schema
  import Ecto.Changeset

  schema "conversations" do
    field :title, :string
    field :start_date_time, :utc_datetime

    timestamps()
  end

  @doc false
  def changeset(conversation, attrs) do
    conversation
    |> cast(attrs, [:title, :start_date_time])
    |> validate_required([:title, :start_date_time])
  end
end

查看

defmodule ThingyWeb.ConversationView do
  use ThingyWeb, :view

  def render("error.json", %{message: message}) do
    %{
      errors: [message]
    }
  end

  def render("created.json", %{conversation: conversation}) do
    render_one(conversation, ConversationView, "conversation.json")
  end

  def render("conversation.json", %{conversation: conversation}) do
    %{
      id: conversation.id,
      title: conversation.title
    }
  end
end

测试

 describe "Conversation operations" do
    setup [:login_user]

    test "able to provide details of a new conversation, and receive the assigned id in response",
         %{
           conn: conn,
           authentication: authentication
         } do
          IO.puts("starting problem test")
      conn =
        conn
        |> put_req_header("authorization", "Bearer " <> authentication.meallogger_token)
        |> put_req_header("content-type", "application/json")
        |> post(
          Routes.conversation_path(conn, :create),
          %{
            title: "new conversation",
            start_date_time: "2024-06-14T15:30:00Z",
          }
        )

      resp = json_response(conn, 201)

      assert %{
               "id" => _id,
             } = resp

      case validate_return_properties(resp, @expected_create_response_properties) do
        {:error, extra_keys} ->
          assert false, "there were extraneous keys in the json response: #{extra_keys}"
      end
    end
  end

注意:其他测试存在并且不会失败,但它们都是空/错误情况测试,因此不会尝试呈现响应

测试输出和失败

starting problem test
store to db went well, now trying to render response
%Metabite.Conversations.Conversation{
  __meta__: #Ecto.Schema.Metadata<:loaded, "conversations">,
  id: 50,
  title: "new conversation",
  start_date_time: ~U[2024-06-14 15:30:00Z],
  inserted_at: ~N[2024-06-07 06:30:42],
  updated_at: ~N[2024-06-07 06:30:42]
}
Mix task exited with reason
normal
returning code 0

1) test Conversation operations able to provide details of a new conversation, and receive the assigned id in response (ThingyWeb.ConversationControllerTest)
     test/thingy_web/controllers/conversation_controller_test.exs:50
     ** (Protocol.UndefinedError) protocol Enumerable not implemented for %{id: 49, title: "new conversation", __struct__: Thingy.Conversations.Conversation, layout: false, inserted_at: ~N[2024-06-07 06:30:40], conn: %Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{id: 49, title: "new conversation", __struct__: Thingy.Conversations.Conversation, layout: false, inserted_at: ~N[2024-06-07 06:30:40], __meta__: #Ecto.Schema.Metadata<:loaded, "conversations">, start_date_time: ~U[2024-06-14 15:30:00Z], updated_at: ~N[2024-06-07 06:30:40], current_user: 1}, body_params: %{"start_date_time" => "2024-06-14T15:30:00Z", "title" => "new conversation"}, cookies: %{}, halted: false, host: "www.example.com", method: "POST", owner: #PID<0.587.0>, params: %{"start_date_time" => "2024-06-14T15:30:00Z", "title" => "new conversation"}, path_info: ["api", "v1", "conversations"], path_params: %{}, port: 80, private: %{ThingyWeb.Router => {[], %{PhoenixSwagger.Plug.SwaggerUI => []}}, :phoenix_view => ThingyWeb.ConversationView, :phoenix_template => "created.json", :phoenix_router => ThingyWeb.Router, :phoenix_endpoint => ThingyWeb.Endpoint, :phoenix_action => :create, :phoenix_controller => ThingyWeb.ConversationController, :before_send => [#Function<0.54455629/1 in Plug.Telemetry.call/2>], :plug_session_fetch => #Function<1.76384852/1 in Plug.Session.fetch_session/1>, :plug_skip_csrf_protection => true, :phoenix_recycled => true, :phoenix_request_logger => {"request_logger", "request_logger"}, :phoenix_format => "json", :phoenix_layout => {ThingyWeb.LayoutView, :app}}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [{"accept", "application/json"}, {"authorization", "Bearer auth-token-123"}, {"content-type", "application/json"}], request_path: "/api/v1/conversations", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "F9alGnAy2l0PbroAAAbB"}], scheme: :http, script_name: [], secret_key_base: :..., state: :unset, status: 201}, __meta__: #Ecto.Schema.Metadata<:loaded, "conversations">, start_date_time: ~U[2024-06-14 15:30:00Z], updated_at: ~N[2024-06-07 06:30:40], current_user: 1} of type Thingy.Conversations.Conversation (a struct). This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, JasonV.OrderedObject, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Stream
     code: |> post(
     stacktrace:
       (elixir 1.16.3) lib/enum.ex:1: Enumerable.impl_for!/1
       (elixir 1.16.3) lib/enum.ex:166: Enumerable.reduce/3
       (elixir 1.16.3) lib/enum.ex:4396: Enum.reverse/1
       (elixir 1.16.3) lib/enum.ex:3726: Enum.to_list/1
       (elixir 1.16.3) lib/map.ex:224: Map.new_from_enum/1
       (phoenix_view 2.0.2) lib/phoenix_view.ex:370: Phoenix.View.render/3
       (phoenix_view 2.0.2) lib/phoenix_view.ex:557: Phoenix.View.render_to_iodata/3
       (phoenix 1.6.15) lib/phoenix/controller.ex:772: Phoenix.Controller.render_and_send/4
       (thingy 0.1.1) lib/thingy_web/controllers/conversation_controller.ex:1: ThingyWeb.ConversationController.action/2
       (thingy 0.1.1) lib/thingy_web/controllers/conversation_controller.ex:1: ThingyWeb.ConversationController.phoenix_controller_pipeline/2
       (phoenix 1.6.15) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
       (thingy 0.1.1) lib/thingy_web/endpoint.ex:1: ThingyWeb.Endpoint.plug_builder_call/2
       (thingy 0.1.1) lib/thingy_web/endpoint.ex:1: ThingyWeb.Endpoint."call (overridable 3)"/2
       (thingy 0.1.1) deps/plug/lib/plug/debugger.ex:136: ThingyWeb.Endpoint."call (overridable 4)"/2
       (thingy 0.1.1) lib/thingy_web/endpoint.ex:1: ThingyWeb.Endpoint.call/2
       (phoenix 1.6.15) lib/phoenix/test/conn_test.ex:225: Phoenix.ConnTest.dispatch/5
       test/thingy_web/controllers/conversation_controller_test.exs:60: (test)
elixir phoenix-framework ecto
1个回答
0
投票

好吧,我修好了,但我不知道为什么会这样。

根据@Dogbert的评论,我在控制器代码中进行了以下更改:

            conn
            |> put_status(201)
            |> render("created.json", conversation: conversation)

添加命名可以解决问题,但指出我从我的角度来看缺少内部别名:

defmodule ThingyWeb.ConversationView do
  use ThingyWeb, :view
  alias ThingyWeb.ConversationView

完成此操作后,根据断言标准,测试按预期失败。

我不确定我是否完全理解为什么这一变化很重要,如果有人可以帮助解释,我将不胜感激。 同样,不确定我是否理解为什么必须将模块别名为自身,以便

render("created.json"
函数能够找到
render("conversation.json"
函数,因此再次感谢任何帮助理解。

无论如何,谢谢Dogbert!

© www.soinside.com 2019 - 2024. All rights reserved.