2015-07-26 5 views
12

Sag mal, habe ich Beitrag Modell, das viele Schlagwörter gehört zu:verwalten many-to-many Verein

defmodule MyApp.Post do 
    use MyApp.Web, :model 

    schema "tours" do 
    field :title, :string 
    field :description, :string 
    has_many :tags, {"tags_posts", MyApp.Tag} 
    end 

    # … 
end 

Wenn ein Beitrag Speichern I tags_ids Liste von Multiselect-Feld wie das erhalten:

tags_ids[]=1&tags_ids[]=2 

Die Frage ist, wie man Tags mit der Post beim Speichern in Phoenix verbindet?

Antwort

9

Verschachtelte Änderungssätze werden im ecto noch nicht unterstützt: https://github.com/elixir-lang/ecto/issues/618 Sie müssen die Tags selbst speichern.

In den folgenden Codeschnipsel werde ich die tag_ids auswählen und sie in die Join-Tabelle einfügen, wenn die Post.changeset/2 mir ein gültiges Ergebnis geben. Um die ausgewählten Tags im Formular zu halten, habe ich ein virtuelles Feld hinzugefügt, das wir im Formular lesen und einen Standard einrichten können. Es ist nicht die beste Lösung, aber es funktioniert für mich.

Postcontroller

def create(conn, %{"post" => post_params}) do 
    post_changeset = Post.changeset(%Post{}, post_params) 

    if post_changeset.valid? do 
    post = Repo.insert!(post_changeset) 

    case Dict.fetch(post_params, "tag_ids") do 
     {:ok, tag_ids} -> 

     for tag_id <- tag_ids do 
      post_tag_changeset = PostTag.changeset(%PostTag{}, %{"tag_id" => tag_id, "post_id" => post.id}) 
      Repo.insert(post_tag_changeset) 
     end 
     :error -> 
     # No tags selected 
    end 

    conn 
    |> put_flash(:info, "Success!") 
    |> redirect(to: post_path(conn, :new)) 
    else 
    render(conn, "new.html", changeset: post_changeset) 
    end 
end 

PostModel

schema "posts" do 
    has_many :post_tags, Stackoverflow.PostTag 
    field :title, :string 
    field :tag_ids, {:array, :integer}, virtual: true 

    timestamps 
end 

@required_fields ["title"] 
@optional_fields ["tag_ids"] 

def changeset(model, params \\ :empty) do 
    model 
    |> cast(params, @required_fields, @optional_fields) 
end 

PostTagModel (JoinTable für viele Vereinigung viele erstellen)

schema "post_tags" do 
    belongs_to :post, Stackoverflow.Post 
    belongs_to :tag, Stackoverflow.Tag 

    timestamps 
end 

@required_fields ["post_id", "tag_id"] 
@optional_fields [] 

def changeset(model, params \\ :empty) do 
    model 
    |> cast(params, @required_fields, @optional_fields) 
end 

PostForm

<%= form_for @changeset, @action, fn f -> %> 

    <%= if f.errors != [] do %> 
    <div class="alert alert-danger"> 
     <p>Oops, something went wrong! Please check the errors below:</p> 
     <ul> 
     <%= for {attr, message} <- f.errors do %> 
     <li><%= humanize(attr) %> <%= message %></li> 
     <% end %> 
     </ul> 
    </div> 
    <% end %> 

    <div class="form-group"> 
    <%= label f, :title, "Title" %> 
    <%= text_input f, :title, class: "form-control" %> 
    </div> 

    <div class="form-group"> 
    <%= label f, :tag_ids, "Tags" %> 
    <!-- Tags in this case are static, load available tags from controller in your case --> 
    <%= multiple_select f, :tag_ids, ["Tag 1": 1, "Tag 2": 2], value: (if @changeset.params, do: @changeset.params["tag_ids"], else: @changeset.model.tag_ids) %> 
    </div> 

    <div class="form-group"> 
    <%= submit "Save", class: "btn btn-primary" %> 
    </div> 

<% end %> 

Wenn Sie Tags aktualisieren möchten, haben Sie zwei Möglichkeiten.

  1. Alles löschen und neue Einträge
  2. Suchen Sie nach Änderungen einzufügen, und halten Sie die vorhandenen Einträge

Ich hoffe, es hilft.

8

Das erste, was Sie tun möchten, ist, die Modelle zu reparieren. Ecto bietet die has_many through: Syntax für viele-zu-viele-Beziehungen. Here are the docs.

Eine Viele-zu-Viele-Beziehung erfordert eine Join-Tabelle, da weder Tags noch Posts direkt aufeinander zeigende Fremdschlüssel haben können (das würde eine Eins-zu-viele-Beziehung erzeugen).

Ecto erfordert, dass Sie die Eins-zu-viele-Join-Tabellenbeziehung unter Verwendung von has_many vor der Viele-zu-Viele-Beziehung definieren, die has_many through: verwendet.

Mit Ihrem Beispiel würde es wie folgt aussehen:

defmodule MyApp.Post do 

    use MyApp.Web, :model 

    schema "posts" do 
    has_many :tag_posts, MyApp.TagPost 
    has_many :tags, through: [:tag_posts, :tags] 

    field :title, :string 
    field :description, :string 
    end 

    # … 
end 

Dies setzt voraus, dass Sie eine Tabelle beitreten tag_posts, die wie etwas aussieht:

defmodule MyApp.TagPost do 

    use MyApp.Web, :model 

    schema "tag_posts" do 
    belongs_to :tag, MyApp.Tag 
    belongs_to :post, MyApp.Post 

    # Any other fields to attach, like timestamps... 
    end 

    # … 
end 

Vergewissern Sie sich, wenn Sie in der Lage sein wollen sehen Sie alle Posts, die zu einem bestimmten Tag gehören, die Sie im Tag-Modell anders definieren:

defmodule MyApp.Tag do 

    use MyApp.Web, :model 

    schema "posts" do 
    has_many :tag_posts, MyApp.TagPost 
    has_many :posts, through: [:tag_posts, :posts] 

    # other post fields 
    end 

    # … 
end 

Dann möchten Sie in Ihrem Controller neue tag_posts mit der ID des Posts, den Sie speichern, und der ID der Tags aus Ihrer Liste erstellen.

+0

ich meine Modelle basierend auf dem hier gegebenen Beispiel erweitert haben, aber ich habe einige Fragen: In MyApp.Tag Sie „#other Post Felder“ schreiben, ich nehme an, Sie „#other Tag-Felder“ In Zukunft bedeuten Wie würde ich zum Beispiel alle Posts mit einem bestimmten Tag oder allen Tags für einen bestimmten Post abfragen? Ich nehme an, diese vorbereiteten Abfragen sollten in das TagPost-Modell eingefügt werden? Ich denke, was ich frage ist, gibt es irgendwelche "Abkürzungen" oder eine vereinfachte Art, dies zu tun? Oder muss ich eine lange Abfrage manuell schreiben? – Wobbley

+0

@Der Broßoror Wie würden Sie den PostController entsprechend den von Ihnen vorgeschlagenen Änderungen ändern? – helcim