我的Event model
start_at
时间和end_at
时间在我的日程安排应用中,我希望在保存之前验证重叠时间。
我在 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]
start_at
时间和end_at
时间可以同时更改和添加。
如果我尝试为07:05 - 07:30
添加(或更改为)Day1
,例如13:50 - 14:30
Day2
,我想要做的就是显示错误等等。
例如;
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
,但验证无效。
虽然我问similar question,但我被建议使用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 < ActiveRecord::Base
belongs_to :room, inverse_of: :events
has_one :schedule, autosave: false, through: :room
...
validate :cannot_overlap_another_event
def cannot_overlap_another_event
range = Range.new start_at, end_at
overlaps = Event.exclude_self(id).in_range(range)
overlap_error unless overlaps.empty?
end
scope :in_range, -> 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
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
<br>
<%= f.label :departure_date %>
<div class="input-group date" id="datetimepicker">
<%= f.text_field :departure_date, :value => (f.object.departure_date if f.object.departure_date), class: 'form-control' %>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
<script type="text/javascript">
$(function () {
$('#datetimepicker').datetimepicker({format:'YYYY-MM-DD'});
});
</script>
<br>
<div id="room">
<%= f.simple_fields_for :rooms do |a| %>
<div id="room_<%= a.object.object_id %>">
<p class="day-number-element-selector"><b>Day <%= a.index.to_i + 1 %></b></p>
<%= a.simple_fields_for :events do |e| %>
<span class="form-inline">
<p>
<%= e.input :start_at, label: false %> -
<%= e.input :end_at, label: false %>
</p>
</span>
<%= e.input :title, label: false %>
<% end %>
</div>
<%= a.link_to_add "Add event", :events, data: {target: "#room_#{a.object.object_id}"}, class: "btn btn-primary" %>
<%= a.input :room %>
<% end %>
</div>
如果您能告诉我如何检查并显示错误,将不胜感激。
修改
编辑如下;
event.rb
scope :in_range, -> range {
where('(start_at BETWEEN ? AND ? OR end_at BETWEEN ? AND ?) OR (start_at <= ? AND end_at >= ?)', range.first, range.last, range.first, range.last, range.first, range.last)
}
虽然它似乎有效但是,当我在event
之后的另一天添加id=8
时,此验证无效。 (请参阅created_at
和updated_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
答案 0 :(得分:5)
你不需要在PostgreSQL中重新发明轮子,有两种实现的简单方法来实现重叠检查:
OVERLAPS
operator:很简单,
where("(start_at, end_at) OVERLAPS (?, ?)", range.first, range.last)
然而,这使得一个范围可以完全在另一个范围之后 (换句话说,它会检查 start&lt; = time&lt; end )。
&&
(overlaps) operator:这通常很简单。但是PostgreSQL没有time
的内置范围类型(但是其他时间类型有tsrange
,tstzrange
和daterange
。
您需要为自己创建此范围类型:
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, '[]')
包含开头和&amp;范围的终点。默认情况下,它包含开头,但不包括范围的终点。
它可以被索引,f.ex。与
CREATE INDEX events_times_idx ON events USING GIST (timerange(start_at, end_at));
Exclusion constraints:这基本上是相同的,您希望实现的目标,但它将在数据库级别强制执行(例如,UNIQUE
或任何其他约束):
ALTER TABLE events
ADD CONSTRAINT events_exclude_overlapping
EXCLUDE USING GIST (timerange(start_at, end_at) WITH &&);
答案 1 :(得分:3)
您的范围是正确的,但并不涵盖您的所有情况。
scope :in_range, -> range {
where('(start_at BETWEEN ? AND ?)', range.first, range.last)
}
在您的示例中,您最终会检查start_at BETWEEN 7:05 AND 7:30
,但第1天的start_at
为7: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
上添加类似的检查以获得代码对称性,但这并不是绝对必要的。