You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ecto中一对一关系的归档方案问询

解决Ecto中Room与Actor的一对一归档关联问题

核心需求回顾

  • 同一时间一个Room仅能关联一个Active状态的Actor
  • Actor离开房间后标记为Archived,需保留与原Room的关联用于归档
  • 必须支持通过some_room.actor获取当前活跃Actor,通过some_actor.room获取关联的Room

解决方案实现

1. 调整Schema关联配置

修改Room的has_one关联,添加状态过滤条件;同时完善Actor的变更集逻辑:

# Actor Model
defmodule MyApp.Actor do
  use Ecto.Schema
  import Ecto.Changeset

  schema "actors" do
    field :actor_name, :string
    field :status, Ecto.Enum, values: [:Active, :Archived], default: :Active
    belongs_to :room, MyApp.Room
  end

  def changeset(actor, attrs) do
    actor
    |> cast(attrs, [:actor_name, :status, :room_id])
    |> validate_required([:actor_name, :status])
  end
end

# Room Model
defmodule MyApp.Room do
  use Ecto.Schema
  import Ecto.Changeset

  schema "rooms" do
    field :room_number, :integer
    # 仅关联Active状态的Actor,确保some_room.actor返回当前活跃实例
    has_one :actor, MyApp.Actor, where: [status: :Active]
  end

  def changeset(room, attrs) do
    room
    |> cast(attrs, [:room_number])
    |> validate_required([:room_number])
  end
end

2. 调整数据库外键约束

在actors表的迁移文件中,设置外键的on_delete行为,避免删除Room时破坏归档数据:

defmodule MyApp.Repo.Migrations.CreateActors do
  use Ecto.Migration

  def change do
    create table(:actors) do
      add :actor_name, :string, null: false
      add :status, :string, null: false
      # 当Room被删除时,将Actor的room_id设为nil(保留其他归档数据)
      add :room_id, references(:rooms, on_delete: :nilify_all)
    end

    # 添加索引优化查询
    create index(:actors, [:room_id, :status])
  end
end

3. 实现Actor切换逻辑

通过事务确保原子性,归档旧Actor并关联新Actor:

defmodule MyApp.RoomActorManager do
  alias MyApp.{Repo, Room, Actor}

  def assign_actor(%Room{} = room, %Actor{} = new_actor) do
    Repo.transaction(fn ->
      # 1. 归档当前房间的活跃Actor(保留room_id)
      if current_actor = Repo.get_by(Actor, room_id: room.id, status: :Active) do
        current_actor
        |> Actor.changeset(%{status: :Archived})
        |> Repo.update!()
      end

      # 2. 将新Actor关联到房间并设为活跃状态
      new_actor
      |> Actor.changeset(%{room_id: room.id, status: :Active})
      |> Repo.insert_or_update!()

      # 返回更新后的Room(预加载当前活跃Actor)
      Repo.preload(room, :actor)
    end)
  end
end

关键逻辑说明

  • Room的has_one关联添加where: [status: :Active],确保预加载或查询时仅返回当前活跃的Actor,满足some_room.actor的访问需求
  • 切换Actor时通过事务保证原子性,避免数据不一致
  • 旧Actor仅修改状态,保留room_id字段,确保some_actor.room仍能访问原关联房间
  • 外键设置on_delete: :nilify_all,防止删除Room时连带删除归档的Actor数据

内容的提问来源于stack exchange,提问作者DaveIdito

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 00:56:09