无法查询关联 table
Can't query associated table
我有一个简单的待办事项/作者模型,其中待办事项有一个 author_id 字段。
模型定义如下:
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, Main.Todo
timestamps()
end
我得到一个
warning: invalid association todo
in schema TodoElixir.User.Author:
associated schema Main.Todo does not exist
待办事项模型:
defmodule TodoElixir.Main.Todo do
use Ecto.Schema
import Ecto.Changeset
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, User.Author
timestamps()
end
我也为每个迁移:
defmodule TodoElixir.Repo.Migrations.CreateAuthors do
use Ecto.Migration
def change do
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
has_many :todos, Main.Todo
timestamps()
end
end
end
defmodule TodoElixir.Repo.Migrations.CreateTodos do
use Ecto.Migration
def change do
create table(:todos) do
add :title, :string
add :description, :string
add :date, :date
add :author_id, references(:authors)
timestamps()
end
end
end
如果我从模块中删除 has_many :todos, Main.Todo
,它会编译并且我可以查询
http://localhost:4000/api/todos 但未设置作者字段。
我试过使用 preload 和 assoc,但在 https://elixirschool.com/en/lessons/ecto/associations/ 之后关联应该是自动的...
在待办事项控制器中我有:
def index(conn, _params) do
todos = Main.list_todos()
render(conn, "index.json", todos: todos)
end
和list_todos=
def list_todos do
Repo.all(Todo)
end
编辑:
我在控制器中输入:
def index(conn, _params) do
todos = Repo.all(Todo) |> Repo.preload(:author)
render(conn, "index.json", todos: todos)
end
我在控制台看到查询:
[debug] Processing with TodoElixirWeb.TodoController.index/2
Parameters: %{} Pipelines: [:api] [debug] QUERY OK source="todos"
db=6.3ms decode=1.7ms queue=0.8ms SELECT t0."id", t0."date",
t0."description", t0."title", t0."author_id", t0."inserted_at",
t0."updated_at" FROM "todos" AS t0 [] [debug] QUERY OK
source="authors" db=0.6ms queue=1.0ms SELECT a0."id", a0."email",
a0."name", a0."hash", a0."inserted_at", a0."updated_at", a0."id" FROM
"authors" AS a0 WHERE (a0."id" = )
我觉得不错,但是 JSON 结果:
{"data":[{"date":null,"description":"we need to do this","id":1,"title":"My first todo"}]}
我是否也应该告诉 Elixir 在 JSON 响应中添加关联?怎么样?
您需要明确 preload 关系:
todos = Main.list_todos()
|> Repo.preload(:todos) # don't forget to alias repo
如果它抛出错误,那么关系没有被正确引用,否则它会进行连接查询,你将拥有 todos
.
中的所有关系
如果您阅读 has_many/3 文档,您会注意到以下内容:
:foreign_key - Sets the foreign key, this should map to a field on the
other schema, defaults to the underscored name of the current schema
suffixed by _id
所以如果你有一个不同名称的外键,你可以明确地使用这个参数:
has_many :todos, Main.Todo, foreign_key: :author_id
此外,您不应该向迁移添加关系,在迁移中您只定义对表所做的结构和修改,因此删除:
has_many :todos, Main.Todo
您可以详细了解在迁移中可以做什么 here。
问题在这里:has_many :todos, Main.Todo
TodoElixir.Repo.Migrations.CreateAuthors
。应该是
defmodule TodoElixir.Repo.Migrations.CreateAuthors do
use Ecto.Migration
def change do
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
timestamps()
end
end
end
预加载数据后即可查询
def list_todos do
Repo.all(Todo)
|> preload(:author)
end
此外,您应该使用 TodoElixir.Main.Todo
而不是 Main.Todo
和 TodoElixir.User.Author
而不是 User.Author
根据需要的要求
I have simple todo / author model where todo has an author_id field that needs to parse as JSON.
- 先迁移
defmodule TodoElixir.Repo.Migrations.CreateAuthorsTodos do
use Ecto.Migration
def change do
# create authors
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
timestamps()
end
flush() # this one will execute migration commands above [see Ecto.Migration flush/0][1]
# create todos
create table(:todos) do
add :title, :string
add :description, :string
add :date, :date
add :author_id, references(:authors)
timestamps()
end
end
end
- 为每个表设置表和关系。您可以查看 Ecto Schema 并查看不同的功能来设置它们。在这种情况下将使用 has_many 和 belongs_to
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, TodoElixir.Main.Todo
timestamps()
end
end
defmodule TodoElixir.User.Todo do
use Ecto.Schema
import Ecto.Changeset
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, TodoElixir.User.Author # -> this will be used upon preload in your controller
timestamps()
end
end
- 在你的控制器中,要预加载你可以这样做
- 首先为您的资源设置别名:作者、Todo 和您的 Repo
- 然后创建函数以调用所有 TODO 预加载 AUTHOR.
alias TodoElixir.User.{Author, Todo} # -> your tables
alias TodoElixir.Repo # -> call your repo
def index(conn, _params) do
todos = list_todos()
render(conn, "index.json", todos: todos)
end
defp list_todos() do
Todo
|> Repo.all()
|> Repo.preload(:author)
end
# in your endpoint.ex
# set up Jason using this one.
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Jason
# in your TODO and AUTHOR schemas derived the fields that you need in each tables.
defmodule TodoElixir.User.Todo do
use Ecto.Schema
import Ecto.Changeset
# this is the key parsing them
@derive Jason.Encoder
defstruct %{
:date,
:description,
:title,
:author # -> This will show author. take note, if you do not preload author via TODO, this will cause error
}
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, TodoElixir.User.Author
timestamps()
end
end
# since we call AUTHOR inside TODO, we also need to derived fields from Author. # Otherwise it will cause error.
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
# you can also call fields that you want to parse.
@derive Jason.Encoder
defstruct %{
:email,
:name,
:id
}
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, TodoElixir.Main.Todo
timestamps()
end
end
- 现在在你的VIEW中,你可以这样设置
def render("index.json", %{todos: todos}) do
todos
end
附加说明:如果您不想在您的架构中派生字段并且仍想将它们解析为json,您可以这样做。
# in your CONTROLLER,
alias TodoElixir.User.{Author, Todo} # -> your tables
alias TodoElixir.Repo # -> call your repo
def index(conn, _params) do
todos = list_todos()
render(conn, "index.json", todos: todos)
end
defp list_todos() do
Todo
|> Repo.all()
|> Repo.preload(:author)
end
# In your VIEW, you can manipulate the transformation you want.
def render("index.json", %{todos: todos}) do
todos
|> Enum.map(fn f ->
%{
# you can add additional fields in here.
title: f.title,
author: f.author.name
}
end)
end
我有一个简单的待办事项/作者模型,其中待办事项有一个 author_id 字段。
模型定义如下:
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, Main.Todo
timestamps()
end
我得到一个
warning: invalid association
todo
in schema TodoElixir.User.Author: associated schema Main.Todo does not exist
待办事项模型:
defmodule TodoElixir.Main.Todo do
use Ecto.Schema
import Ecto.Changeset
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, User.Author
timestamps()
end
我也为每个迁移:
defmodule TodoElixir.Repo.Migrations.CreateAuthors do
use Ecto.Migration
def change do
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
has_many :todos, Main.Todo
timestamps()
end
end
end
defmodule TodoElixir.Repo.Migrations.CreateTodos do
use Ecto.Migration
def change do
create table(:todos) do
add :title, :string
add :description, :string
add :date, :date
add :author_id, references(:authors)
timestamps()
end
end
end
如果我从模块中删除 has_many :todos, Main.Todo
,它会编译并且我可以查询
http://localhost:4000/api/todos 但未设置作者字段。
我试过使用 preload 和 assoc,但在 https://elixirschool.com/en/lessons/ecto/associations/ 之后关联应该是自动的...
在待办事项控制器中我有:
def index(conn, _params) do
todos = Main.list_todos()
render(conn, "index.json", todos: todos)
end
和list_todos=
def list_todos do
Repo.all(Todo)
end
编辑:
我在控制器中输入:
def index(conn, _params) do
todos = Repo.all(Todo) |> Repo.preload(:author)
render(conn, "index.json", todos: todos)
end
我在控制台看到查询:
[debug] Processing with TodoElixirWeb.TodoController.index/2
Parameters: %{} Pipelines: [:api] [debug] QUERY OK source="todos" db=6.3ms decode=1.7ms queue=0.8ms SELECT t0."id", t0."date", t0."description", t0."title", t0."author_id", t0."inserted_at", t0."updated_at" FROM "todos" AS t0 [] [debug] QUERY OK source="authors" db=0.6ms queue=1.0ms SELECT a0."id", a0."email", a0."name", a0."hash", a0."inserted_at", a0."updated_at", a0."id" FROM "authors" AS a0 WHERE (a0."id" = )
我觉得不错,但是 JSON 结果:
{"data":[{"date":null,"description":"we need to do this","id":1,"title":"My first todo"}]}
我是否也应该告诉 Elixir 在 JSON 响应中添加关联?怎么样?
您需要明确 preload 关系:
todos = Main.list_todos()
|> Repo.preload(:todos) # don't forget to alias repo
如果它抛出错误,那么关系没有被正确引用,否则它会进行连接查询,你将拥有 todos
.
如果您阅读 has_many/3 文档,您会注意到以下内容:
:foreign_key - Sets the foreign key, this should map to a field on the other schema, defaults to the underscored name of the current schema suffixed by _id
所以如果你有一个不同名称的外键,你可以明确地使用这个参数:
has_many :todos, Main.Todo, foreign_key: :author_id
此外,您不应该向迁移添加关系,在迁移中您只定义对表所做的结构和修改,因此删除:
has_many :todos, Main.Todo
您可以详细了解在迁移中可以做什么 here。
问题在这里:has_many :todos, Main.Todo
TodoElixir.Repo.Migrations.CreateAuthors
。应该是
defmodule TodoElixir.Repo.Migrations.CreateAuthors do
use Ecto.Migration
def change do
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
timestamps()
end
end
end
预加载数据后即可查询
def list_todos do
Repo.all(Todo)
|> preload(:author)
end
此外,您应该使用 TodoElixir.Main.Todo
而不是 Main.Todo
和 TodoElixir.User.Author
而不是 User.Author
根据需要的要求
I have simple todo / author model where todo has an author_id field that needs to parse as JSON.
- 先迁移
defmodule TodoElixir.Repo.Migrations.CreateAuthorsTodos do
use Ecto.Migration
def change do
# create authors
create table(:authors) do
add :name, :string
add :email, :string
add :hash, :string
timestamps()
end
flush() # this one will execute migration commands above [see Ecto.Migration flush/0][1]
# create todos
create table(:todos) do
add :title, :string
add :description, :string
add :date, :date
add :author_id, references(:authors)
timestamps()
end
end
end
- 为每个表设置表和关系。您可以查看 Ecto Schema 并查看不同的功能来设置它们。在这种情况下将使用 has_many 和 belongs_to
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, TodoElixir.Main.Todo
timestamps()
end
end
defmodule TodoElixir.User.Todo do
use Ecto.Schema
import Ecto.Changeset
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, TodoElixir.User.Author # -> this will be used upon preload in your controller
timestamps()
end
end
- 在你的控制器中,要预加载你可以这样做
- 首先为您的资源设置别名:作者、Todo 和您的 Repo
- 然后创建函数以调用所有 TODO 预加载 AUTHOR.
alias TodoElixir.User.{Author, Todo} # -> your tables
alias TodoElixir.Repo # -> call your repo
def index(conn, _params) do
todos = list_todos()
render(conn, "index.json", todos: todos)
end
defp list_todos() do
Todo
|> Repo.all()
|> Repo.preload(:author)
end
# in your endpoint.ex
# set up Jason using this one.
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Jason
# in your TODO and AUTHOR schemas derived the fields that you need in each tables.
defmodule TodoElixir.User.Todo do
use Ecto.Schema
import Ecto.Changeset
# this is the key parsing them
@derive Jason.Encoder
defstruct %{
:date,
:description,
:title,
:author # -> This will show author. take note, if you do not preload author via TODO, this will cause error
}
schema "todos" do
field :date, :date
field :description, :string
field :title, :string
belongs_to :author, TodoElixir.User.Author
timestamps()
end
end
# since we call AUTHOR inside TODO, we also need to derived fields from Author. # Otherwise it will cause error.
defmodule TodoElixir.User.Author do
use Ecto.Schema
import Ecto.Changeset
# you can also call fields that you want to parse.
@derive Jason.Encoder
defstruct %{
:email,
:name,
:id
}
schema "authors" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hash, :string
has_many :todos, TodoElixir.Main.Todo
timestamps()
end
end
- 现在在你的VIEW中,你可以这样设置
def render("index.json", %{todos: todos}) do
todos
end
附加说明:如果您不想在您的架构中派生字段并且仍想将它们解析为json,您可以这样做。
# in your CONTROLLER,
alias TodoElixir.User.{Author, Todo} # -> your tables
alias TodoElixir.Repo # -> call your repo
def index(conn, _params) do
todos = list_todos()
render(conn, "index.json", todos: todos)
end
defp list_todos() do
Todo
|> Repo.all()
|> Repo.preload(:author)
end
# In your VIEW, you can manipulate the transformation you want.
def render("index.json", %{todos: todos}) do
todos
|> Enum.map(fn f ->
%{
# you can add additional fields in here.
title: f.title,
author: f.author.name
}
end)
end