2016-02-28 10 views
11

Ich habe zwei Modelle, Song und Vote, wo Songs viele Stimmen hat. Ich möchte alle Lieder auswählen und die Anzahl der Stimmen für jeden zählen.Wie Ecto Select Abfragen in Strukturen in Phoenix drehen?

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 enthält eine Liste von Listen In diesem Fall:

Der Index Aktion im SongController, die Mischung gen Aufgabe erzeugt wird, dazu wurde geändert. Aber in der ursprünglichen, generierten Funktion, songs = Repo.all(Song) ist es eine Liste von Song-Strukturen.

Dies bedeutet, dass die song_path Funktionen in der Vorlage Bruch mit der folgenden Fehlermeldung: maps cannot be converted to_param. A struct was expected, got: %{artist: "Stephen", id: 3, name: "Crossfire"}

Natürlich, was ich wirklich ist zu tun, wollen ein num_votes Feld der select-Anweisung irgendwie hinzufügen und dann Irgendwie ein entsprechendes Feld zur Song-Struktur machen?

+0

Ich sehe, dass Hex.pm löst ein ähnliches Problem (Anzahl der Downloads eines Pakets) durch völlig separate Listen. ([Controller] (https://github.com/hexpm/hex_web/blob/master/web/controllers/package_controller.ex#L24), [Anzeigen] (https://github.com/hexpm/hex_web/blob/ Meister/Web/Vorlagen/Paket/index.html.eex # L67)) – Torstein

Antwort

15

Zuerst sollten wir ein virtual field zum Song-Schema hinzufügen, so dass es verwendet werden kann, das num_votes Ergebnis zu speichern:

defmodule Song do 
    use Ecto.Schema 

    schema "songs" do 
    field :num_votes, :integer, virtual: true 
    ... 
    end 
end 

Mit einer Kombination aus Ecto.Query.select/3, Ecto.Query.join/5 und Ecto.Query.API.count/1 wir die Zählungen auf die Karte hinzufügen Sie verwenden aus der Abfrage zu wählen:

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

Wir dann Kernel.struct jedes Element in einer Struktur konvertieren können:

songs = 
    query 
    |> Repo.all() 
    |> Enum.map(fn(song) -> struct(Song, song) end) 

Dies gibt eine Liste der Songstrukturen zurück, die in der Ansicht verwendet werden können.

+2

gibt es eine Möglichkeit, nicht alle Felder im ausgewählten Teil der Abfrage aufzulisten, aber sagen Sie zu ecto "hey, wählen Sie alle Felder aus dem Schema sowie diese num_votes: count() Spalte"? – vsushkov

+2

für ein virtuelles Feld habe ich Lösung gefunden: 'select:% {s | num_votes: count (v.id)} ', aber ich habe mehr als einen hinzugefügt. – luzny

1

Eine interessante Sache zu beachten ist, dass Strukturen sind eigentlich nur dicts mit einem __struct__ Schlüssel auf die Modul-Namen, die sie ein Teil sind. Aus diesem Grund können Sie ein normales Struct in ein Dict verwandeln, indem Sie einfach den Schlüssel __struct__ entfernen.

iex(1)> defmodule M do 
...(1)> defstruct [:a, :b] 
...(1)> end 

iex(2)> Map.delete(%M{}, :__struct__) 
%{a: nil, b: nil} 

(Referenz: https://groups.google.com/forum/#!topic/elixir-lang-talk/2xQqFOZSvk0)

auch immer Sie in die andere Richtung gehen wollen, so dass es einfach ist, es einfach auf die gleiche Weise hinzufügen Map.add verwenden. Bitte beachten Sie, um dies zur Arbeit zu bringen alle Schlüssel müssen da sein, auch wenn Sie nur auf nil setzen.

Also für den anderen Teil von dir sind Fragen. Es gibt wahrscheinlich eine schicke SQL-Methode, um die Zählungen zu erhalten. Ich würde dir empfehlen, das zu tun. Ich für einen würde es wahrscheinlich einfach zusammen in Elixier mit einem Join und dann Enum.map darüber hacken und die Zählungen mit einer ganzen Zahl anstelle einer Liste ersetzen. Hier ist ein Artikel über die Joins: http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/.

Ich überlasse es Ihnen, wie Sie das tun.