无法查询关联 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.TodoTodoElixir.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
  • 现在呈现与作者关联的 json,让我们回到 TODO 和 AUTHOR 模式
  • 要将它们加载为 JSON,您可以使用 JASON or POISON
  • 对于这个我们将使用 JASON
# 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


  1. 现在在你的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