【问题标题】:Unable to create many to many associations in Ecto无法在 Ecto 中创建多对多关联
【发布时间】:2016-09-16 06:04:39
【问题描述】:

我正在开发一个多用户、多房间聊天应用程序,我的模型如下(为简单起见,省略了 App 模型):

defmodule Elemental.TxChat.User do
  use Elemental.TxChat.Web, :model

  schema "users" do
    # The rooms the user is currenly logged into
    many_to_many :rooms, Elemental.TxChat.Room, join_through: "rooms_users"
    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [])
    |> validate_required([])
  end
end

defmodule Elemental.TxChat.Room do
  use Elemental.TxChat.Web, :model

  schema "rooms" do
    field :name, :string    
    # The user id that created this room
    field :created_by, :integer
    field :created_from_app, :integer

    many_to_many :members, Elemental.TxChat.User, join_through: "rooms_users"

    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :created_from_app, :created_by])
    |> validate_required([:name, :created_by, :created_from_app])
  end
end

接下来,我从iex 创建了一些房间和用户(每个三个)。现在我想知道:假设我希望 user1 属于 room1 和 room2,并且 user2 属于 room2 和 room3 ......怎么办?

在我看来,虽然定义模式很好,但必须有一个中间步骤来执行类似user1.rooms = [room1, room2] 的操作。所以我最终访问了this post,并看到了build_assoc 的示例:

Ecto.build_assoc(current_user, :post)

所以这个应用程序有用户和帖子,并试图链接它们。但我不明白它是如何实现的。数据库/Ecto 如何知道哪个用户 id 与哪个帖子 id 链接?

无论如何,我尝试在我的应用程序的iex 中执行此操作:

iex(46)> Ecto.build_assoc(user1, :rooms, room1)
%Elemental.TxChat.Room{__meta__: #Ecto.Schema.Metadata<:built, "rooms">,
 created_by: 1, created_from_app: 1, id: 2,
 inserted_at: #Ecto.DateTime<2016-09-16 05:00:00>,
 members: #Ecto.Association.NotLoaded<association :members is not loaded>,
 name: "room1", updated_at: #Ecto.DateTime<2016-09-16 05:00:00>}

我认为函数调用会将user1 加入模型Room,使用room1 中的数据来查找目标房间。当我在输出中看到&lt;association :members is not loaded&gt; 时,我的心沉了下去,但我想我应该检查一下数据库。你猜怎么着,我的加入表(rooms_users)中没有条目。 :(

我认为很明显需要以某种方式创建模型之间的这些链接,但我似乎碰壁了。如何做到这一点?

【问题讨论】:

    标签: elixir phoenix-framework ecto


    【解决方案1】:

    编辑
    旁注:为 users_rooms 表创建 UserRoom 模型并创建与 UserRoom.changeset/2 的关联会容易得多

    原始答案

    我做了一个小例子项目

    defmodule Playground.User do
      use Playground.Web, :model
      alias __MODULE__
    
      schema "users" do
        field :title, :string
        many_to_many :rooms, Playground.Room, join_through: "users_rooms"
    
        timestamps()
      end
    
      def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, [:title])
        |> validate_required([:title])
      end
    
      def assoc_changeset(struct, params \\ %{}) do
        struct
        |> cast(params, [:title])
        |> validate_required([:title])
        |> add_rooms(params, struct)
      end
    
      defp add_rooms(changeset, params, %User{rooms: rooms}) do
        case params do
          %{add_rooms: to_be_added} when is_list(to_be_added) ->
            changeset |> put_assoc(:rooms, rooms ++ to_be_added)
          _ ->
            changeset
        end
      end
    end
    

    它是如何工作的:

    iex(1)> u = User.changeset(%User{}, %{title: "u"}) |> Repo.insert!
    iex(2)> r1 = Room.changeset(%Room{}, %{title: "r1"}) |> Repo.insert!
    iex(3)> r2 = Room.changeset(%Room{}, %{title: "r2”}) |> Repo.insert!
    
    iex(4)> u = User.changeset(u, %{title: "u1"}) |> Repo.update!
    %Playground.User{
      rooms: #Ecto.Association.NotLoaded<association :rooms is not loaded>,
      title: "u1",
      ...
    }
    
    iex(5)> u = User.assoc_changeset(u, %{add_rooms: [r1]}) |> Repo.update!
    error about #Ecto.Association.NotLoaded<association :rooms is not loaded> here
    
    iex(6)> u = Repo.preload(u, :rooms)
    %Playground.User{
      rooms: [],
      title: "u1",
      ...
    }
    
    iex(7)> u = User.assoc_changeset(u, %{add_rooms: [r1]}) |> Repo.update!
    %Playground.User{
      rooms: [
        %Playground.Room{title: "r1", ...}
      ],
      title: "u1",
      ...
    }
    
    iex(7)> u = User.assoc_changeset(u, %{add_rooms: [r2]}) |> Repo.update!
    %Playground.User{
      rooms: [
        %Playground.Room{title: "r1", ...},
        %Playground.Room{title: "r2", ...}
      ],
      title: "u1",
      ...
    }
    

    仍有改进的余地。辅助函数 add_rooms/3 可能应该从变更集中而不是第三个参数中获取用户房间,并更改为 add_rooms/2。
    由您决定还有哪些需要改进的地方。

    【讨论】:

    • 旁注:为 users_rooms 表制作 UserRoom 模型并创建与 UserRoom.changeset/2 的关联会容易得多
    • 我的上帝。 . .那是一个比我想要的更复杂的丢失。你是对的:创建模型是更清洁(更好?)的方式。感谢您的投入和时间。 :-)
    • 我将编辑我的答案并添加“旁注”以开始让其他有同样问题的人更容易。
    • 好多了。现在我正在努力在凤凰城完成一个项目,这就是为什么我必须选择更简单的方法。但是,仅表选项是否真的较差,或者它在某些条件下是否提供优势,这是有道理的。也许以后做一个练习!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-30
    • 1970-01-01
    • 2016-06-02
    • 1970-01-01
    • 2013-09-08
    相关资源
    最近更新 更多