使用 Ecto 插入前查询记录(类似于 AR 回调)

Query records before insertion with Ecto (similar to an AR callback)

我是 Elixir 和 Phoenix 的新手(不到 10 天),但对此非常兴奋,并且像许多其他人一样,我有 Rails 背景。

我知道 Ecto 不是 AR,并且回调已被弃用或删除,但我需要添加一个自定义验证,该验证只应在创建时发生并且需要执行查询。

这是我的 Reservation 模型的基本样子。

schema "reservations" do
  field :ends_at, :utc_datetime
  field :name, :string, null: false
  field :starts_at, :utc_datetime
  field :user_id, :id
end

然后我有另一个架构 Slot,它看起来像这样:

schema "slots" do
  field :ends_at, :utc_datetime
  field :name, :string, null: false
  field :starts_at, :utc_datetime
  field :admin_id, :id
end

每当我添加新预订时,我都需要查询我的数据库以检查是否有匹配 ends_atstarts_at 的空位。如果有,我需要阻止保存记录并向其添加错误(类似于 Rails 中我们用 throw :aborterrors.add 完成的内容)。

有人可以解释一下吗? Ecto 的做法是什么?

此致

*编辑:添加了使用单独的变更集进行创建和更新的示例

您可以在变更集验证链中添加自定义验证函数并在其中进行数据库查询。

没有运行这个代码,但是像这样的东西应该可以工作

# separate changeset for creation
def create_changeset(struct, params) do
  struct
  |> cast(params, [...list of fields...])
  |> validate_unique([:name]) # lets say it has to be unique
  |> validate_slots # -- custom validation
end

# separate changeset for updation, no slot-check
def update_changeset(struct, params) do
  struct
  |> cast(params, [...list of fields...])
  |> validate_unique([:name]) # lets say it has to be unique
end


def validate_slots(changeset) do
    starts_at = get_field(changeset, :starts_at)
    ends_at = get_field(changeset, :ends_at)
    slots = Repo.all(from s in Slot, where: s.starts_at == ^starts_at and s.ends_at == ^ends_at)

    if Enum.empty?(slots) do
      changeset
    else
      add_error( changeset, :starts_at, "has slot with similar starts_at/ends_at")
    end
end

#---- using the changesets
# creation
%Reservation{} |> Reservation.create_changeset(params) |> Repo.insert()

# updation
%Reservation{} |> Reservation.update_changeset(params) |> Repo.update()

不过,从表面上看,您可能应该将 starts_at 和 ends_at 规范化为单独的 table,称为 booking_time_frame 或其他名称,并添加唯一索引到它。

或者您最终可能会有更多类型的预订,然后必须在 3 table 秒内检查 starts_at/ends_at,依此类推。