Rails:验证数据库级别的日期范围唯一性

时间:2016-03-14 12:16:52

标签: ruby-on-rails postgresql indexing

我有一个带有这3个型号的Rails应用程序 - RentingUnit,Tenant&预订。

租户可以通过填写这些字段的新预订表格来预订租赁单位 - renting_unit_id,tenant_id,start_date,end_date。

start_date& end_date一起形成了renting_unit预订的持续时间。

有了这个,我想确保在一个与已经预订的任何持续时间重叠的期限内不能预订renting_unit。 (如果重要的话,我正在使用PostgreSQL数据库。)

我遇到了模型级验证的相关答案,但我也希望在数据库级别强制执行唯一性,以便考虑可能的竞争条件。

我该如何实施呢?

2 个答案:

答案 0 :(得分:1)

在数据库中执行此类操作确实是一个明智的想法。幸运的是,PostgreSQL使这很容易。我不知道在rails中是否有内置支持,如果不是你想要运行一些自定义SQL。

CREATE TABLE booking (tenant_id int, unit_id int, start_date date, end_date date);
-- This is the constraint that prevents overlapping bookings
ALTER TABLE booking ADD constraint no_overlap 
  EXCLUDE USING gist (
    unit_id WITH =, 
    daterange(start_date, end_date, '[]') WITH &&
  );
-- These two abut, so that's fine
INSERT INTO booking VALUES (101, 1, '2015-01-01', '2015-01-31');
INSERT INTO booking VALUES (102, 1, '2015-02-01', '2015-02-31');
-- This one overlaps the second one, so it should fail
INSERT INTO booking VALUES (103, 1, '2015-02-01', '2015-02-01');
ERROR:  conflicting key value violates exclusion constraint "no_overlap"
DETAIL: ...

注意事项。

  1. 您需要安装btree_gist扩展名以支持相等性检查
  2. 日期范围(...,' []')表示该范围的两端都包含在内。你当然可以选择和独家范围。
  3. 可以说你应该直接存储范围而不是单独的日期,因为这是你想要建模的。
  4. 我自己更喜欢复数表名,但我不知道rails喜欢什么。
  5. 详情和示例一如既往地在PostgreSQL Documentation

答案 1 :(得分:0)

我有一个类似的问题,上面的答案对我不起作用。

以下内容确实存在:

execute <<~SQL
  ALTER TABLE booking
  ADD CONSTRAINT unique_time_range_per_unit_id
  EXCLUDE USING GIST (
    (unit_id::text) WITH =,
    tsrange(start_date, end_data) WITH &&
  );
SQL

在Postgres 9.6上测试