【问题标题】:Ecto: Repo.preload not working, protocol Ecto.Queryable not implementedEcto:Repo.preload 不工作,协议 Ecto.Queryable 未实现
【发布时间】:2017-10-25 22:31:43
【问题描述】:

我安装了全新的 Phoenix 1.3 并生成了博客上下文,其中包含几个模式 Post 和 Comment。我的测试项目名为 NewVersion(因为我正在测试 Phoenix 框架的新版本)。

所以,我的架构非常标准,主要由 phx.gen.html 生成:

Post.ex

defmodule NewVersion.Blog.Post do
  use Ecto.Schema
  import Ecto.Changeset
  alias NewVersion.Blog.Post


  schema "blog_posts" do
    field :body, :string
    field :title, :string
    has_many :comments, NewVersion.Blog.Comment, on_delete: :delete_all, foreign_key: :blog_post_id

    timestamps()
  end

  @doc false
  def changeset(%Post{} = post, attrs) do
    post
    |> cast(attrs, [:title, :body])
    |> validate_required([:title, :body])
    |> cast_assoc(:comments)
  end
end

comment.ex

defmodule NewVersion.Blog.Comment do
  use Ecto.Schema
  import Ecto.Changeset
  alias NewVersion.Blog.Comment


  schema "blog_comments" do
    field :body, :string
    belongs_to :blog_post, NewVersion.Blog.Post, foreign_key: :blog_post_id

    timestamps()
  end

  @doc false
  def changeset(%Comment{} = comment, attrs) do
    comment
    |> cast(attrs, [:body])
    |> validate_required([:body])
  end
end

blog.ex

defmodule NewVersion.Blog do
  @moduledoc """
  The boundary for the Blog system.
  """

  import Ecto.Query, warn: false
  alias NewVersion.Repo

  alias NewVersion.Blog.Post

  def list_posts do
    Repo.all(Post)
  end

  def get_post!(id), do: Repo.get!(Post, id)

  def create_post(attrs \\ %{}) do
    %Post{}
    |> Post.changeset(attrs)
    |> Repo.insert()
  end

  def update_post(%Post{} = post, attrs) do
    post
    |> Post.changeset(attrs)
    |> Repo.update()
  end

  def delete_post(%Post{} = post) do
    Repo.delete(post)
  end

  def change_post(%Post{} = post) do
    Post.changeset(post, %{})
  end

  alias NewVersion.Blog.Comment

  def list_comments do
    Repo.all(Comment)
  end

  def get_comment!(id), do: Repo.get!(Comment, id)

  def create_comment(attrs \\ %{}) do
    %Comment{}
    |> Comment.changeset(attrs)
    |> Repo.insert()
  end

  def update_comment(%Comment{} = comment, attrs) do
    comment
    |> Comment.changeset(attrs)
    |> Repo.update()
  end

  def delete_comment(%Comment{} = comment) do
    Repo.delete(comment)
  end

  def change_comment(%Comment{} = comment) do
    Comment.changeset(comment, %{})
  end    

end

一切正常,但我想创建嵌套表单以使用他们的 cmets 创建和编辑帖子,为此我想将 cmets 预加载到帖子中。在指南 (https://github.com/elixir-ecto/ecto/blob/master/guides/Associations.md) 中,我找到了一个帖子和标签示例,如下所示:

tag = Repo.get(Tag, 1) |> Repo.preload(:posts)

但是当我改变函数get_post!在我的 blog.ex 中类似的方式来自:

  def get_post!(id), do: Repo.get!(Post, id)

到:

  def get_post!(id), do: Repo.get!(Post, id) |> Repo.preload(:comments)

我收到一个错误:没有为 %NewVersion.Blog.Post{meta 实现协议 Ecto.Queryable:#Ecto.Schema.Metadata<:loaded>, body: "This是第二个帖子”,cmets: [%NewVersion.Blog.Comment{meta: ...

这是为什么呢?似乎我严格遵循文档,但缺少一些东西。 我只是看不出我的代码和那里提供的代码之间的区别,当然我知道实现协议的方法。

顺便说一句,如果我把我的功能改成这样:

def get_post!(id), do: Repo.get!(Post |> preload(:comments), id) 

错误消失了。但我仍然想知道为什么第一种方法对我不起作用。

堆栈跟踪的完整错误消息是:

[error] #PID<0.8148.0> running NewVersion.Web.Endpoint terminated
Server: localhost:4000 (http)
Request: GET /posts/2/edit
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Ecto.Queryable not implemented for %Ne
wVersion.Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "blog_posts">, body:
 "Blog post 2", comments: [%NewVersion.Blog.Comment{__meta__: #
Ecto.Schema.Metadata<:loaded, "blog_comments">, blog_post: #Ecto.Association.Not
Loaded<association :blog_post is not loaded>, blog_post_id: 2, body: "third comm
ent", id: 3, inserted_at: ~N[2017-05-22 12:49:37.506000], title: "Comment 3", up
dated_at: ~N[2017-05-22 12:49:37.506000], votes: 1}, %NewVersion.Blog.Comment{__
meta__: #Ecto.Schema.Metadata<:loaded, "blog_comments">, blog_post: #Ecto.Associ
ation.NotLoaded<association :blog_post is not loaded>, blog_post_id: 2, body: "Comment for blog post 2", id: 2, inserted_at: ~N[2
017-05-22 12:14:48.590000], title: "Comment 2 for post 2", updated_at: ~N[2017-0
5-22 12:14:48.590000]}], id: 2, inserted_at: ~N[2017-05-22 12:14:47.96
2000], title: "Post 2", updated_at: ~N[2017-05-22 12:14:47.962000]}
        (ecto) lib/ecto/queryable.ex:1: Ecto.Queryable.impl_for!/1
        (ecto) lib/ecto/queryable.ex:9: Ecto.Queryable.to_query/1
        (ecto) lib/ecto/query/builder/preload.ex:154: Ecto.Query.Builder.Preload
.apply/3
        (new_version) lib/new_version/web/controllers/post_controller.ex:33: New
Version.Web.PostController.edit/2
        (new_version) lib/new_version/web/controllers/post_controller.ex:1: NewV
ersion.Web.PostController.action/2
        (new_version) lib/new_version/web/controllers/post_controller.ex:1: NewV
ersion.Web.PostController.phoenix_controller_pipeline/2
        (new_version) lib/new_version/web/endpoint.ex:1: NewVersion.Web.Endpoint
.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (new_version) lib/new_version/web/endpoint.ex:1: NewVersion.Web.Endpoint
.plug_builder_call/2
        (new_version) lib/plug/debugger.ex:123: NewVersion.Web.Endpoint."call (o
verridable 3)"/2
        (new_version) lib/new_version/web/endpoint.ex:1: NewVersion.Web.Endpoint
.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Hand
ler.upgrade/4
        (cowboy) d:/test/new_version/deps/cowboy/src/cowboy_protocol.erl:442: :c
owboy_protocol.execute/4

来自 PostController 的编辑功能:

  def edit(conn, %{"id" => id}) do
    post = Blog.get_post!(id)
    IO.inspect post
    changeset = Blog.change_post(post)
    render(conn, "edit.html", post: post, changeset: changeset)
  end

【问题讨论】:

  • 你确定这个表达式:Repo.get!(Post, id) |&gt; Repo.preload(:comments) 会抛出那个错误吗?
  • 是的,到目前为止我的代码没有更多变化。只有这一行,之后出现错误
  • 你能发布完整的错误消息,包括堆栈跟踪吗?
  • 在问题底部添加了堆栈跟踪。
  • 看起来错误是从New Version.Web.PostController.edit/2 触发的。您可以发布该功能的来源吗? (也最好标出post_controller.ex的第33行是哪一行。)

标签: elixir phoenix-framework ecto


【解决方案1】:

正如我已经在我的问题的 cmets 中写的那样,最初的错误是我没有准确地遵循 Ecto 文档,而不是函数

def get_post!(id), do: Repo.get!(Post, id) |> preload(:comments)

我应该有(注意缺少 Repo。)

def get_post!(id), do: Repo.get!(Post, id) |> Repo.preload(:comments)

但比这更糟糕的是,我的问题没有提供具有误导性的错误代码。

结论:问题没有价值,预加载关联没有问题。凤凰社区的支持很棒,对新手很友好,谢谢你们!因为你,我觉得phoenix框架对我来说真的是不错的选择。

编辑:

想了想怎么会这样

def get_post!(id), do: Repo.get!(Post |> preload(:comments), id) 

工作,并且

def get_post!(id), do: Repo.get!(Post, id) |> preload(:comments)

没有,我决定为大家详细说明答案。

原因是预加载函数取自 Ecto.Query 模块,而不是取自 NewVersion.Repo 模块(NewVersion 只是我项目的名称)。

这个函数期望得到 Ecto.Queryable 作为第一个参数。在第一种情况下,提供了原子 Post(它只是 NewVersion.Blog.Post 的别名),如果您查看https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/queryable.ex,您会看到为原子提供了 Ecto.Queryable 协议。至于第二个表达式,Repo.get!(Post, id) 返回 Post 结构而不是 Post atom,并且没有为它提供 Ecto.Queryable 协议。

【讨论】:

    猜你喜欢
    • 2016-06-16
    • 2015-02-24
    • 2018-10-19
    • 2018-11-23
    • 2016-09-10
    • 1970-01-01
    • 2017-07-01
    • 2017-11-26
    • 2017-04-19
    相关资源
    最近更新 更多