我安装了新的凤凰1.3,并生成了带有几个模式的博客上下文,并在其中发表了评论。我的测试项目名为NewVersion (因为我正在测试菲尼克斯框架的新版本)。
因此,我的模式是相当标准的,主要由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
endcomment.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
endblog.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一切正常,但我想创建嵌套表单,用于创建和编辑带有注释的帖子,为此,我希望将注释预加载到posts中。在指南(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.Schema.Metadata<:loaded实现的协议Ecto.Queryable,"blog_posts">,body:“这是第二篇文章”,注释:[%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发布于 2017-05-25 15:43:32
正如我在对我的问题的评论中所写的,最初的错误是我没有精确地跟踪Ecto docs,而不是函数
def get_post!(id), do: Repo.get!(Post, id) |> preload(:comments)我应该(通知失踪的回购)。
def get_post!(id), do: Repo.get!(Post, id) |> Repo.preload(:comments)但比这更糟糕的是,我的问题并没有为实际的代码提供错误,这是误导性的。
得出的结论是:这个问题没有价值,预压联合也没有问题。凤凰城社区的支持是伟大的,新手友好,谢谢,伙计们!因为你,我觉得菲尼克斯框架真的是我很好的选择。
编辑:
在思考了一下为什么
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作为第一个参数。在第一种情况下,提供了atom Post (只是NewVersion.Blog.Post的别名),如果您查看https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/queryable.ex,您可以看到Ecto.Queryable协议是为原子提供的。至于第二个表达式,Repo.get!( Post,id)返回Post结构而不是Post原子,并且没有为其提供Ecto.Queryable协议。
https://stackoverflow.com/questions/44177705
复制相似问题