无法查询关联表

时间:2019-12-10 15:25:48

标签: elixir phoenix-framework

我有一个简单的待办事项/作者模型,其中待办事项具有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

我在这里得到一个

  

警告:模式TodoElixir.User.Author中的无效关联todo:   关联的架构Main.Todo不存在

和待办事项模型:

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,但未设置作者字段。

我尝试使用预加载和assoc,但是在https://elixirschool.com/en/lessons/ecto/associations/之后,关联应该是自动的...

在todo控制器中,我有:

  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]使用TodoElixirWeb.TodoController.index / 2进行处理
  参数:%{}管道:[:api] [调试] QUERY OK source =“ todos”   db = 6.3ms解码= 1.7ms队列= 0.8ms SELECT t0。“ id”,t0。“ date”,   t0。“描述”,t0。“标题”,t0。“ author_id”,t0。“ insertted_at”,   t0。“ updated_at”来自“待办事项”,为t0 [] [debug]查询成功   source =“ authors” db = 0.6ms队列= 1.0ms SELECT a0。“ id”,a0。“ email”,   a0。“名称”,a0。“哈希”,a0。“ insertted_at”,a0。“ updated_at”,a0。“ id” FROM   “作者”为a a0哪里(a0。“ id” = $ 1)

对我来说哪个看起来不错,但是JSON结果:

{"data":[{"date":null,"description":"we need to do this","id":1,"title":"My first todo"}]}

我还应该告诉Elixir在JSON响应中添加关​​联吗?怎么样?

3 个答案:

答案 0 :(得分:1)

您需要明确preload关系:

todos = Main.list_todos()
|> Repo.preload(:todos) # don't forget to alias repo

如果引发错误,则关系未正确引用,否则将进行联接查询,并且您将在todos中拥有所有关系。

如果您阅读has_many/3文档,则会注意到以下内容:

  

:foreign_key-设置外键,该外键应映射到   其他模式,默认为当前模式的带下划线的名称   _id后缀

因此,如果您有一个外键名不同,则可以显式使用此参数:

has_many :todos, Main.Todo, foreign_key: :author_id

此外,您不应添加与迁移的关系,在迁移中,您仅定义对表所做的结构和修改,因此请删除:

has_many :todos, Main.Todo

您可以了解有关在迁移here中可以做什么的更多信息。

答案 1 :(得分:1)

根据所需要求

  

我有一个简单的待办事项/作者模型,其中待办事项具有一个author_id字段,需要将其解析为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预加载的作者
  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,可以使用JASONPOISON
  • 为此,我们将使用 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

答案 2 :(得分:0)

问题在这里:)上的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