我有两个模型,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/。
至于如何做到这一点,我就交给你了。