2015-07-26 14 views
12

powiedzmy, mam modelu post, który należy do wielu Tagi:Zarządzanie wiele-do-wielu stowarzyszenia

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 

Podczas zapisywania postu uzyskać listę tags_ids z pola multiselect tak:

tags_ids[]=1&tags_ids[]=2 

Pytanie brzmi, jak powiązać tagi z postem w Save in Phoenix?

Odpowiedz

9

Zagnieżdżone zestawy zmian nie są jeszcze obsługiwane w ecto: https://github.com/elixir-lang/ecto/issues/618 Musisz samodzielnie zapisywać tagi.

W poniższych fragmentach kodu wybiorę tag_ids i wstawię je do tabeli sprzężenia, jeśli Post.changeset/2 da mi prawidłowy wynik. W celu zatrzymania wybranych znaczników w formularzu I dodano wirtualne pole, które możemy odczytać w formularzu i ustawić domyślne. To nie jest najlepsze rozwiązanie, ale działa dla mnie.

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 do tworzenia wielu wielu związku)

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 %> 

Jeśli chcesz zaktualizować tagi, masz dwie opcje.

  1. Usuń wszystkie kryteria i wstawić nowe wpisy
  2. Poszukaj zmian i zachować istniejące wpisy

Mam nadzieję, że to pomaga.

8

Pierwszą rzeczą, którą chcesz zrobić, to naprawić modele. Ecto zapewnia składnię has_many through: dla relacji wielu do wielu. Here are the docs.

Relacja wiele do wielu wymaga tabeli sprzężenia, ponieważ żadne tagi ani posty nie mogą mieć kluczy obcych wskazujących bezpośrednio na siebie (co tworzy relację jeden-do-wielu).

Ecto wymaga zdefiniowania relacji tabeli jeden do wielu przy użyciu has_many przed relacją wiele do wielu, która używa has_many through:.

Dzięki swoim przykładzie, to będzie wyglądać:

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 

ta zakłada, że ​​masz dołączyć tabelę tag_posts, który wygląda mniej więcej tak:

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 

upewnić się, czy chcesz, aby móc zobacz wszystkie posty powiązane z danym tagiem, aby zdefiniować relację w inny sposób w modelu Tag:

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 

Następnie, w swoim kontrolerze, chcesz utworzyć nowe tag_posts z zarówno ID zapisywanego wpisu, jak i identyfikatorem tagów z listy.

+0

I Rozszerzyliśmy moich modeli opartych na przykładzie podanym tutaj, ale mam kilka pytań: W MyApp.Tag Ci napisać „#other pól post” Zakładam, że masz na myśli „#other pola TAG” W przyszłości w jaki sposób mogę na przykład zapytać o wszystkie posty z określonym tagiem lub wszystkie tagi dla określonego posta? Zakładam, że te przygotowane zapytania należy umieścić w modelu TagPost? Chyba pytam, czy istnieją jakieś "skróty" lub uproszczony sposób robienia tego? Czy będę musiał ręcznie napisać długie zapytanie? – Wobbley

+0

@ Brofessor Jak zmieniłbyś PostController, aby odzwierciedlał zmiany, które sugerujesz? – helcim

Powiązane problemy