Как включить запросы Ecto в структуры в Phoenix?
У меня есть две модели: Song and 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"}
Конечно, что я действительно хочу сделать, так или иначе добавить поле num_votes
в оператор select, а затем каким-то образом создать соответствующее поле в структуре Song?
Ответы
Ответ 1
Сначала мы должны добавить виртуальную область в схему песни, чтобы ее можно было использовать для хранения результата 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)
Это возвращает список структур песен, которые можно использовать в представлении.
Ответ 2
Интересно отметить, что структуры на самом деле просто задают ключом __struct__
, установленным для имени модуля, частью которого они являются. Из-за этого вы можете превратить обычный Struct в Dict, просто удалив клавишу __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-способ, чтобы получить счет. Я бы рекомендовал вам это сделать. Я для одного, возможно, просто взломал бы его вместе в elixir, используя соединение, а затем Enum.map
ing над ним и заменив counts целым числом, а не списком. Вот статья о том, как выполнять объединения: http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/.
Я оставлю это вам, как это сделать.