如何使用postgresqlvalidationRails中的重叠时间

我有一个Event model ,在我的日程安排应用程序中有end_at时间和end_at时间,我想在保存之前validation重叠时间。

我在Cloud9上创建了rails应用程序。

我的观点图如下;

 Day1 07:00 - 07:20 event1 10:30 - 11:30 event2 15:40 - 16:10 event3 [add event button] Day2 08:15 - 09:05 event4 12:08 - 13:04 event5 14:00 - 14:25 event6 [add event button] [save schedule button] 

end_at time和end_at时间可以同时更改和添加。

我想做的是显示错误,如果我尝试为Day1添加(或更改) 07:05 - 07:30 ,例如, 13:50 - 14:30 ,依此类推。

例如;

app_development =#select * from events;

  id | start_at | end_at | title | detail | schedule_id | created_at | updated_at ----+----------+----------+--------+--------+-----------------+----------------------------+---------------------------- 1 | 07:00:00 | 07:20:00 | event1 | | 1 | 2016-04-12 05:28:44.166827 | 2016-04-12 12:52:07.682872 2 | 10:30:00 | 11:30:00 | event2 | | 1 | 2016-04-12 05:28:44.17747 | 2016-04-12 12:52:07.689934 3 | 15:40:00 | 16:10:00 | event3 | | 1 | 2016-04-12 05:29:07.5005 | 2016-04-12 12:52:07.693477 

我在表格上方添加了07:05 - 07:30 ,但validation不起作用。

虽然我问了类似的问题 ,但我被建议使用postgresql而不是sqlite3。

所以我设法配置postgresql,但结果是一样的。 如果你能告诉我如何检查和显示错误,将不胜感激。

schema.rb

  create_table "events", force: :cascade do |t| t.time "start_at" t.time "end_at" t.string "title" t.integer "room_id" ... create_table "rooms", force: :cascade do |t| t.string "room" t.integer "schedule_id" ... create_table "schedules", force: :cascade do |t| t.string "title" t.integer "user_id" t.date "departure_date" ... 

提供以下型号

 class Event  range { where('(start_at BETWEEN ? AND ?)', range.first, range.last) } scope :exclude_self, -> id { where.not(id: id) } def overlap_error errors.add(:overlap_error, 'There is already an event scheduled in this hour!') end class Schedule < ActiveRecord::Base belongs_to :user has_many :rooms, inverse_of: :schedule accepts_nested_attributes_for :rooms, allow_destroy: true ... class Room < ActiveRecord::Base belongs_to :schedule, inverse_of: :rooms has_many :events, inverse_of: :room accepts_nested_attributes_for :events, allow_destroy: true ... 

_schedule_form.html.erb

     
(f.object.departure_date if f.object.departure_date), class: 'form-control' %>
$(function () { $('#datetimepicker').datetimepicker({format:'YYYY-MM-DD'}); });
<div id="room_">

Day 

   -   

如果你能告诉我如何检查和显示错误,将不胜感激。

编辑

编辑如下;

event.rb

 scope :in_range, -> range { where('(start_at BETWEEN ? AND ? OR end_at BETWEEN ? AND ?) OR (start_at = ?)', range.first, range.last, range.first, range.last, range.first, range.last) } 

虽然它似乎有效,但是当我在不同的日子添加event时,此validation不起作用,如下面的id=8 。 (参见created_atupdated_at

app_development =#select * from events;

 id | start_at | end_at | title | detail | room_id | created_at | updated_at ----+----------+----------+--------+--------+-----------------+----------------------------+---------------------------- 1 | 07:00:00 | 07:20:00 | event1 | | 1 | 2016-04-12 05:28:44.166827 | 2016-04-12 12:52:07.682872 2 | 10:30:00 | 11:30:00 | event2 | | 1 | 2016-04-12 05:28:44.17747 | 2016-04-12 12:52:07.689934 3 | 15:40:00 | 16:10:00 | event3 | | 1 | 2016-04-12 05:29:07.5005 | 2016-04-12 12:52:07.693477 8 | 07:05:00 | 07:10:00 | event4 | | 1 | 2016-04-15 21:37:58.569868 | 2016-04-15 21:39:27.956737 

您的范围是正确的,但不包括您的所有情况。

 scope :in_range, -> range { where('(start_at BETWEEN ? AND ?)', range.first, range.last) } 

在您的示例中,您最终检查start_at BETWEEN 7:05 AND 7:30 ,但第1天的start_at7:00 ,这超出了该范围。

您需要处理四种情况:

 New range overlaps start Existing: |------------| New: |-------| New range overlaps end Existing: |------------| New: |-------| New range inside existing range Existing: |------------| New: |-------| Existing range inside new range Existing: |-------| New: |------------| 

看,你可以看到前三个案例是通过检查是否来处理

 new_start BETWEEN start_at AND end_at OR new_end BETWEEN start_at AND end_at 

然后你只需要通过添加来捕获第四种情况

 OR start_at BETWEEN new_start AND new_end 

您可以在end_at上添加类似的检查以获得代码对称性,但并不是绝对必要的。

你不需要在PostgreSQL中重新发明轮子,有两种实现的简单方法来实现重叠检查:

  1. SQL的OVERLAPS运算符 :

很简单,

 where("(start_at, end_at) OVERLAPS (?, ?)", range.first, range.last) 

然而,这使得一个范围可以完全在另一个范围之后
(换句话说,它检查开始<=时间<结束 )。

  1. 范围类型’ && (重叠)运算符 :

这通常很简单。 但PostgreSQL在时间上没有内置范围类型(​​但是对于其他时间类型存在tsrangetstzrangedaterange )。

您需要为自己创建此范围类型:

 CREATE TYPE timerange AS RANGE (subtype = time); 

但在此之后,您可以检查重叠

 where("timerange(start_at, end_at) && timerange(?, ?)", range.first, range.last) 

范围类型的优点:

  • 你可以控制自己,你想如何处理范围边界

    f.ex. 你可以使用timerange(start_at, end_at, '[]')来包含范围的起点和终点。 默认情况下,它包含开始,但不包括范围的终点。

  • 它可以被索引,f.ex。 同

     CREATE INDEX events_times_idx ON events USING GIST (timerange(start_at, end_at)); 
  • 排除约束 :这基本上是相同的,你想要实现的,但它将在数据库级别强制执行(如UNIQUE或任何其他约束):

     ALTER TABLE events ADD CONSTRAINT events_exclude_overlapping EXCLUDE USING GIST (timerange(start_at, end_at) WITH &&);