Как превратить запросы Ecto select в структуры в Phoenix?

У меня есть две модели, Song и Vote, в которых песни имеют много голосов. Я хочу выбрать все песни и подсчитать количество голосов за каждую.

Действие index в SongController, созданное с помощью задачи создания микширования, было изменено на следующее:

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"}

Конечно, я действительно хочу как-то добавить поле num_votes в оператор select, а затем каким-то образом сделать соответствующее поле в структуре Song?


person Torstein    schedule 28.02.2016    source источник
comment
Я вижу, что Hex.pm решает аналогичную проблему (количество загрузок пакета), имея полностью отдельные списки. (Контроллер, Просмотр)   -  person Torstein    schedule 28.02.2016


Ответы (2)


Сначала мы должны добавить виртуальное поле в схему песни, чтобы оно могло использоваться для хранения 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)

Это возвращает список структур песни, которые можно использовать в представлении.

person Gazler    schedule 28.02.2016
comment
есть ли способ не перечислять все поля в выбранной части запроса, а сказать ecto, эй, выбрать все поля из схемы плюс этот столбец num_votes: count ()? - person vsushkov; 28.06.2016
comment
для одного виртуального поля я нашел решение: select: %{s | num_votes: count(v.id)}, но я застрял на добавлении более одного. - person luzny; 24.08.2016

Следует отметить одну интересную вещь: структуры на самом деле представляют собой просто dicts с ключом __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. Я бы порекомендовал вам это сделать. Я, например, вероятно, просто взломал бы его вместе в эликсире, используя соединение, а затем Enum.map над ним и заменив счетчики целым числом, а не списком. Вот статья о том, как выполнять объединения: http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/.

Я предоставляю вам решать, как это сделать.

person JustGage    schedule 28.02.2016