2015-07-26 2 views
12

Сай, у меня есть сообщение модель, которая принадлежит многим Теги:Управление многие ко многим ассоциации

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 

При сохранении Post я получаю список tags_ids из MULTISELECT поля так:

tags_ids[]=1&tags_ids[]=2 

Вопрос в том, как связать теги с сообщением о сохранении в Phoenix?

ответ

9

Вложенные изменения еще не поддерживаются в ecto: https://github.com/elixir-lang/ecto/issues/618 Вы должны сохранить теги самостоятельно.

В следующих фрагментах кода я выберу tag_ids и вставляю их в таблицу соединений, если Post.changeset/2 дает мне действительный результат. Для удержания выбранных тегов в Форме I добавлено виртуальное поле, которое мы можем прочитать в форме и установить значение по умолчанию. Это не лучшее решение, но оно работает для меня.

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 для создания многих многих ассоциаций)

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

Если вы хотите обновить тег, у вас есть два варианта.

  1. Удалить все и вставить новые записи
  2. Посмотрите на изменения, и сохранить существующие записи

Я надеюсь, что это помогает.

8

Первое, что вы хотите сделать, это исправить модели. Ecto предоставляет синтаксис has_many through: для отношений «многие ко многим». Here are the docs.

Для отношений «многие ко многим» требуется таблица соединений, поскольку ни теги, ни сообщения не могут иметь внешние ключи, указывающие прямо друг на друга (что создало бы отношения «один ко многим»).

Ecto требует, чтобы вы определяли отношение таблицы соединения «один ко многим», используя has_many до отношения «многие ко многим», которое использует has_many through:.

С вашего примера, это будет выглядеть так:

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 

Это предполагает, что у вас есть присоединиться к таблице tag_posts который выглядит примерно так:

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 

Убедитесь, если вы хотите, чтобы иметь возможность см. все сообщения, связанные с данным тегом, которые вы определяете в другом виде в модели тегов:

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 

Затем в вашем контроллере вы хотите создать новые теги tag_post с идентификатором сохраненного сообщения и идентификатором тегов из вашего списка.

+0

Я расширил мои модели, основанные на вашем примере, приведенных здесь, но у меня есть несколько вопросов: В MyApp.Tag вы пишете «#Other почтовых полей», я полагаю, вы имеете в виду «#Other полех тегов» В будущем как я, например, запрошу все сообщения с определенным тегом или все теги для определенного сообщения? Я предполагаю, что эти подготовленные запросы должны быть помещены в модель TagPost? Я предполагаю, что я спрашиваю, есть ли какие-либо «ярлыки» или упрощенный способ сделать это? Или мне придется вручную написать длинный запрос? – Wobbley

+0

@ The Brofessor Как бы вы изменили PostController, чтобы отразить изменения, которые вы предлагаете? – helcim