Подтвердить что ты не робот

Как включить запросы 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?

4b9b3361

Ответ 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/.

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