如何将 Ecto select 查询转换为 Phoenix 中的结构?
How to turn Ecto select queries into structs in Phoenix?
我有两个模型,Song 和 Vote,其中 songs 有很多选票。我想 select 所有歌曲并计算每首歌曲的票数。
使用mix gen任务生成的SongController中的index动作修改为:
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_path 函数中断并显示以下错误消息:maps cannot be converted to_param. A struct was expected, got: %{artist: "Stephen", id: 3, name: "Crossfire"}
当然,我真正想做的是在select语句中以某种方式添加一个num_votes
字段,然后以某种方式制作相应的字段到 Song 结构?
需要注意的一件有趣的事情是,结构实际上只是带有 __struct__
键的字典,键设置为它们所属的模块名称。因此,您只需删除 __struct__
键即可将普通 Struct 转换为 Dict。
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 方法来获取计数。我建议你这样做。我个人可能只是使用连接在 elixir 中将其组合在一起,然后 Enum.map
遍历它并用整数而不是列表替换计数。这是一篇关于如何进行联接的文章:http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/。
我留给你怎么做。
首先我们应该在歌曲模式中添加一个 virtual field 以便它可以用来存储 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 and Ecto.Query.API.count/1 的组合,我们可以将计数添加到您正在使用的地图中 select 来自查询:
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)
此 returns 可在视图中使用的歌曲结构列表。
我有两个模型,Song 和 Vote,其中 songs 有很多选票。我想 select 所有歌曲并计算每首歌曲的票数。
使用mix gen任务生成的SongController中的index动作修改为:
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_path 函数中断并显示以下错误消息:maps cannot be converted to_param. A struct was expected, got: %{artist: "Stephen", id: 3, name: "Crossfire"}
当然,我真正想做的是在select语句中以某种方式添加一个num_votes
字段,然后以某种方式制作相应的字段到 Song 结构?
需要注意的一件有趣的事情是,结构实际上只是带有 __struct__
键的字典,键设置为它们所属的模块名称。因此,您只需删除 __struct__
键即可将普通 Struct 转换为 Dict。
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 方法来获取计数。我建议你这样做。我个人可能只是使用连接在 elixir 中将其组合在一起,然后 Enum.map
遍历它并用整数而不是列表替换计数。这是一篇关于如何进行联接的文章:http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/。
我留给你怎么做。
首先我们应该在歌曲模式中添加一个 virtual field 以便它可以用来存储 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 and Ecto.Query.API.count/1 的组合,我们可以将计数添加到您正在使用的地图中 select 来自查询:
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)
此 returns 可在视图中使用的歌曲结构列表。