我正在开发一个多用户,多房间聊天应用程序,我的模型如下所示(为简单起见省略了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
中的数据来查找目标会议室。当我在输出中看到<association :members is not loaded>
时,我的心沉了下去,但我想我应该检查数据库。猜猜看,我的加入表中没有条目(rooms_users)。 :(
我认为很明显,模型之间的这些联系需要以某种方式创建,但我似乎已经碰壁了。怎么做?
答案 0 :(得分: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可能应该从changeset而不是第三个参数中取出用户房间并更改为add_rooms / 2。
您需要决定还需要改进哪些内容。