Elixir ecto 2创建了many_to_many关联

时间:2016-02-20 22:12:42

标签: elixir phoenix-framework ecto

如何与ecto 2建立多对多关系?作为我想要的示例应用程序 创建一个可以分为多个类别的帖子。这些类别已经存在。例如:

[%Category{id: "1", name: "elixir"}, %Category{id: "2", name: "erlang"}]

我正在使用Ecto 2 beta 0.示例项目名为Ecto2。

我定义了两个模型:

defmodule Ecto2.Post do
  use Ecto2.Web, :model
  use Ecto.Schema

  schema "posts" do
    field :title, :string
    many_to_many :categories, Ecto2.Category, join_through: "posts_categories", on_replace: :delete
    timestamps
  end

  @required_fields ~w(title)
  @optional_fields ~w()
  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> cast_assoc(:categories)  # not suitable?
  end
end

defmodule Ecto2.Category do
  use Ecto2.Web, :model

  schema "categories" do
    field :name, :string

    timestamps
  end

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

我尝试这样做:

post = Repo.get!(Post, 1) |> Repo.preload(:categories)
changeset = Post.changeset(post, %{"title"=> "bla", "categories"=> [%{id: "1"}]})
Repo.update!(changeset)

但是Post.changeset中的cast_assoc不适合这个任务,它想要创建一个全新的Category而不是关联的一个。 我应该用什么呢? build_assoc?但是build_assoc文档没有提到它对many_to_many很有用。我该如何使用它?我应该将build_assoc放在Post.changeset中,还是应该在phoenix控制器中使用它。

2 个答案:

答案 0 :(得分:16)

您可以通过传递类似于" posts_categories"的字符串,或通过传递MyApp.PostCategory等架构的模式来加入表。我更喜欢通过模式加入,因为时间戳可以包含在内。假设您选择通过模式而不是表格加入:

  1. 您需要为要加入的many_to_many关系创建单独的表格(例如:posts_categories)。
  2. ```

    def change do
      create table(:posts_categories) do
        add :post_id, references(:posts)
        add :category_id, references(:categories)
        timestamps
      end
    end
    
    1. 为您在步骤1中创建的表创建架构。在您的web \ models文件夹中,创建一个文件post_category.ex:
    2. ```

      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已更改:清空为空地图并将演员\ 4更改为演员\ 3。检查更改日志。

      1. 将此行添加到帖子架构中:

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

      2. 将此行添加到您的类别架构中:

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

        那就是它!现在你可以更新了 ```

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

        ```

答案 1 :(得分:2)

经过一夜安眠和在ecto单元测试中的一些挖掘后,我找到了一个部分答案。正确的调用函数是Ecto.Changeset.put_assoc。它返回一个变更集。问题的其余部分在答复的底部。

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

这是一个部分解决方案,因为如果我想更新相关类别(Post已经有相关类别列表),我必须首先删除然后保存空类别列表。 可以一次性完成吗?

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