2017-05-19 3 views
1

Ich habe gelesen auf polymorphic associations in Ecto ziemlich, und ich stimme der Meinung, dass eine native Datenbank Referenz zwischen Ihren Tabellen vorteilhaft ist.Polymorph has_many in Ecto

In den meisten Fällen wird jedoch der polymorphe belongs_to referenziert, nicht umgekehrt (has_many). Ich bin mir immer noch nicht sicher, wie ich damit richtig umgehen soll.

In meinem Fall handelt es sich um eine Phoenix Backend API, ich habe ein Page Modell, das eine Nummer Widgets hat. Jedes dieser Widgets hat eine eigene Tabelle und ein eigenes Modell, da es verschiedene Felder speichern und zurückgeben muss. Nehmen wir an, wir haben eine TwoColumnWidget und eine ThreeColumnWidget, die beide eine Referenz page_id an die Page Modell haben.

Wie würde ich dies idealerweise modellieren, jetzt habe ich eine Zwischenmodell Widget (mit Datenbank), die eine Spalte für jeden möglichen Widget-Typ hat und wählt die, wo eine ID in einer der Spalten vorhanden ist. Das fühlt sich ziemlich hackig an, da ich für jedes Widget, das synchronisiert werden muss, eine zusätzliche Zeile in der Datenbank speichern muss, sowie den Aufwand, den richtigen Serializer/View für jedes konkrete Widget zu erhalten.

Da meine Domain viele dieser Arten von Beziehungen hat, möchte ich im Allgemeinen eine bessere Lösung finden, um die weitere Entwicklung zu erleichtern. Irgendwelche Zeiger?

+1

Wenn Sie sagen "mit einer eigenen Datenbank", haben Sie wirklich mir eine komplette Datenbank oder "eine eigene Tabelle"? –

Antwort

1

Ich antworte das unter der Annahme, dass jeder Widget-Typ seine eigene Tabelle (Schema) hat. Da Sie zwischen den Widgets und Seiten scheinbar eine Eins-zu-Viele-Beziehung und keine Viele-zu-Viele-Beziehung haben, sollten Sie eine einfache Anordnung von Karten in Betracht ziehen, vorausgesetzt, Sie verwenden eine db, die sie unterstützt wie Postgreß.

Sie müssen sich darüber im Klaren sein, dass Karten in der Datenbank mit String-Schlüsseln und nicht mit Atomschlüsseln gespeichert werden. Wenn wir also eine Struktur in der Datenbank speichern, wird sie beim Zurücklesen eine Zuordnung mit Zeichenfolgenschlüsseln sein. So müssen Sie es wieder in eine Karte wie diese Stimmen:

defmodule MyApp.Utils do 

    def cast(%{} = schema, params) do 
    struct schema, map_to_atom_keys(params) 
    end 
    def cast(module, params) when is_atom(module), do: cast(module.__struct__, params) 

    def map_to_atom_keys(%{} = params) do 
    Enum.reduce(params, %{}, fn({k, v}, acc) -> 
     Map.put(acc, to_atom(k), v) 
    end) 
    end 

    defp to_atom(key) when is_atom(key), do: key 
    defp to_atom(key) when is_binary(key), do: String.to_existing_atom(key) 

    def item_type(%{} = item), do: item_type(item.__struct__) 
    def item_type(item) do 
    Module.split(item) 
    |> Enum.reverse 
    |> hd 
    |> to_string 
    end 
end 

Sie etwas ähnliches tun könnte mit einem widgets Tabelle, wobei die variablen Daten als Karte gespeichert ist. Wenn Sie den Namen der Struktur speichern, können Sie ihn in die Struktur zurückschreiben.

defmodule Widget do 
    schema "widgets" do 
    field :embedded_type, :string 
    belongs_to :page, Page 
    field :widget, :map 
    end 

    def changeset(struct, params) do 
    struct 
    |> cast(params, [:embedded_type, :page_id, :widget]) 
    |> handle_widget(params) 
    end 
    defp handle_widget(changeset, %{widget: widget}) do 
    changeset 
    |> put_change(:embedded_type, widget.__struct__ |> inspect) 
    end 
end 

Und dann könnten Sie den obigen Code verwenden, um es zurück zu werfen.

Sie können auch jeden Widget-Typ mit einem embedded_schema erstellen und Ecto verwenden, um das Casting für Sie durchzuführen.