如何将 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 可在视图中使用的歌曲结构列表。