如何在 Phoenix 中将 Ecto 选择查询转换为结构



我有两个模型,Song和Vote,其中歌曲有很多票。我想选择所有歌曲并计算每首歌曲的票数。

SongController 中的索引操作(使用 mix gen 任务生成)已修改为:

def index(conn, _params) do
  query = from s in Song, select: %{id: s.id, name: s.name, artist: s.artist} 
  songs = Repo.all(query)
  render(conn, "index.html", songs: songs)
end

在本例中,songs包含列表列表。但在原始的生成函数中,songs = Repo.all(Song)它是一个 Song 结构的列表。

这意味着模板中的song_path函数会中断,并显示以下错误消息:maps cannot be converted to_param. A struct was expected, got: %{artist: "Stephen", id: 3, name: "Crossfire"}

当然,我真正想做的是以某种方式在 select 语句中添加一个num_votes字段,然后以某种方式为 Song 结构体创建一个相应的字段?

首先,

我们应该在歌曲模式中添加一个虚拟字段,以便它可以用于存储num_votes结果:

defmodule Song do
  use Ecto.Schema
  schema "songs" do
    field :num_votes, :integer, virtual: true
    ...
  end
end
结合使用 Ecto.Query.select/

3、Ecto.Query.join/5 和 Ecto.Query.API.count/1,我们可以将计数添加到您用于从查询中进行选择的地图中:

  query = from s in Song,
    left_join: v in assoc(:votes),
    select: %{id: s.id, name: s.name, artist: s.artist, num_votes: count(v.id)} 

然后,我们可以使用 Kernel.struct 将每个项目转换为结构体:

  songs =
    query
    |> Repo.all()
    |> Enum.map(fn(song) -> struct(Song, song) end)

这将返回可在视图中使用的歌曲结构的列表。

需要注意的一件有趣的事情是,结构实际上只是字典,其中__struct__键设置为它们所属的模块名称。因此,您可以通过简单地删除__struct__键将普通结构转换为字典。

iex(1)> defmodule M do
...(1)> defstruct [:a, :b]
...(1)> end
iex(2)> Map.delete(%M{}, :__struct__)
%{a: nil, b: nil}

(参考资料:https://groups.google.com/forum/#!topic/elixir-lang-talk/2xQqFOZSvk0)

但是,您想朝另一个方向前进,因此很容易使用 Map.add 以相同的方式添加它。请注意,要使其正常工作,所有键都必须在那里,即使您只是将它们设置为 nil .

所以对于你的另一部分是问题。可能有一些花哨的SQL方法来获取计数。我建议你这样做。我可能会使用连接在灵药中将其破解在一起,然后Enum.map它并用整数而不是列表替换计数。以下是一篇关于如何进行联接的文章:http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/。

至于如何做到这一点,我就交给你了。

最新更新