Elixir ecto 2 create many_to_many association

后端 未结 2 560
后悔当初
后悔当初 2020-12-16 15:52

How can I make a many to many relation with ecto 2? As an example app I want to create a Post which can be in multiple categories. The categories already exist. For example

相关标签:
2条回答
  • 2020-12-16 16:13

    After a good night of sleep and some digging in the ecto unit tests i have found a partial answer. The right function to call is Ecto.Changeset.put_assoc. It returns a changeset. The rest of the question is on the bottom of this reply.

    def run_insert_1 do
      c1 = Repo.get!(Category, 1)
      c2 = %Category{name: "cat 2"}
    
      # Inserting
      changeset =
        %Post{title: "1"}
        |> Ecto.Changeset.change
        |> Ecto.Changeset.put_assoc(:categories, [c1, c2])
      post = Repo.insert!(changeset)
      IO.inspect post
    end
    
    def run_insert_2 do
      c1 = Repo.insert! %Category{name: "cat 1"}
      c2 = %Category{name: "cat 2"}
    
      # Inserting
      changeset =
        %Post{title: "1"}
        |> Ecto.Changeset.change
        |> Ecto.Changeset.put_assoc(:categories, [c1, c2])
      post = Repo.insert!(changeset)
      IO.inspect post
    end
    
    def run_update do
      c1 = Repo.insert! %Category{name: "cat update"}
      c2 = %Category{name: "cat 2"}
      post = Repo.get!(Post, 1) |> Repo.preload(:categories)
      # Updating
      changeset =
        post
        |> Ecto.Changeset.change
        |> Ecto.Changeset.put_assoc(:categories, [c1])
      post = Repo.update!(changeset)
      IO.inspect post
    end
    

    It is a partial solution, because if i want to update the related categories (Post already has a list of related categories) I have to remove and then save the empty list of categories first. Is it possible to do this in one go?

    def run_update_2 do
      c2 = Repo.get!(Tag, 2)
      # Assumes Post 1 already has  a few categories in it (for example after
      # running run_update()
      post = Repo.get!(Post, 1) |> Repo.preload(:categories)
    
      # Remove and add again
      changeset =
        post
        |> Ecto.Changeset.change
        |> Ecto.Changeset.put_assoc(:categories, [])
      IO.inspect changeset
      post = Repo.update!(changeset)
    
      changeset =
        post
        |> Ecto.Changeset.change
        |> Ecto.Changeset.put_assoc(:categories, [c2])
    
      post = Repo.update!(changeset)
      IO.inspect post
    end
    
    0 讨论(0)
  • 2020-12-16 16:35

    You can join through a table by passing a string like "posts_categories", or through a schema by passing through a schema like MyApp.PostCategory. I prefer joining through schema as timestamps can be included. Let say you choose join through a schema instead of a table:

    1. You need to create a separate table (e.g. :posts_categories) for the many_to_many relationships to join to.

    ```

    def change do
      create table(:posts_categories) do
        add :post_id, references(:posts)
        add :category_id, references(:categories)
        timestamps
      end
    end
    
    1. Create a schema for the table you created in step 1. In your web\models folder, create a file post_category.ex:

    ```

    defmodule Ecto2.PostCategory do
    use Ecto2.Web, :model
    
    schema "posts_categories" do
      belongs_to :post, Ecto2.Post
      belongs_to :category, Ecto2.Category
      timestamps
    end
    
    def changeset(model, params \\ %{}) do
      model
      |> cast(params, [])
    end
    end
    

    Ecto beta 2 has changed :empty to empty map and change cast\4 to cast \3. Check changelog.

    1. Add this line to your post schema:

      many_to_many :categories, Ecto2.Category, join_through: Ecto2.PostCategory

    2. Add this line to your category schema:

    many_to_many :posts, Ecto2.Post, join_through: Ecto2.PostCategory

    That's it! Now you can update like ```

    post1 = Repo.get!(Post, 1)
    category1 = Repo.get!(Category, 1)
    
    post1
    |> Repo.preload(:categories)
    |> Post.changeset(%{})
    |> put_assoc(:categories, [category1])
    |> Repo.update!
    

    ```

    0 讨论(0)
提交回复
热议问题