这两个点是什么意思.. 在轨道中
有一个这样的函数:
def period
start_date..end_date
end
这是什么?这会生成一个数组吗? 我看到这门课用了这个
class Booking < ApplicationRecord
# ... some code is skipped here for simplicity's sake
validate :validate_other_booking_overlap
def period
start_date..end_date
end
private
def validate_other_booking_overlap
other_bookings = Booking.all
is_overlapping = other_bookings.any? from |other_booking|
period.overlaps?(other_booking.period)
end
errors.add(:overlaps_with_other) if is_overlapping
end
end
这是 ruby 中包含有限Range
的文字语法。
范围表示一个间隔 - 一组具有开始和结束的值...用作迭代器时,范围返回序列中的每个值。
范围可以表示为包含的两个点 (..
),或排除的三个点 (...
)。独占范围不包括最后一个元素。
在 2.6 之前,所有范围都是有限的(语法上需要开始和结束;尽管结尾可以Float::INFINITY
[相反-Float::INFINITY
开始],所以从技术上讲,并非所有范围都是"有限的")。
在 2.6 中,"无限范围"是使用语法x..
实现的。
从 2.7 开始,范围可以使用语法"无开始"..x
根据文档,无始和无尽的范围都是"半无限的",可以表示为包容性或排他性。(老实说,我不确定排他性的无尽范围是什么;尽管语法有创造性的用途[见下文])
作为旁注:(TL;DR)
从性能的角度来看,遍历每个Booking
是一个坏主意。
更好的主意是使用以下方法将其卸载到数据库:
def validate_other_booking_overlap
overlaps_bookings = Booking
.where(start_date: period)
.or(Booking.where(end_date: period))
.or(
Booking.where(
Booking.arel_table[:start_date].gt(start_date)
.and(Booking.arel_table[:end_date].lt(end_date))))
.where.not(id: id)
.exists?
errors.add(:overlaps_with_other) if overlaps_bookings
end
Rails (Arel) 将 WHERE 条件中的有限范围转换为 SQL BETWEEN 子句,因此此查询条件为:
WHERE
((bookings.start_date BETWEEN '####-##-##' AND '####-##-##'
OR bookings.end_date BETWEEN '####-##-##' AND '####-##-##')
OR (
bookings.start_date > '####-##-##'
AND bookings.end_date < '####-##-##'
))
AND bookings.id != # -- IS NOT NULL on create
在较新版本的rails(>=6.0.3和ruby>= 2.7)中,这是:
Booking.arel_table[:start_date].gt(start_date)
.and(Booking.arel_table[:end_date].lt(end_date))
可以用这个代替
Booking.where(start_date: start_date..., end_date:...end_date))
因为现在 Rails (Arel) 将范围处理如下:
x..y
x 和 y- 之间的属性(例如属性>= x 和属性 <= y )
x...y
属性>= x 和属性x..
属性>= xx...
属性> x..x
属性 <= x...x
属性
。现在我相信我们已经走了一个完整的圈子。
更新
由于@max的评论,您可以使用OVERLAPS
函数(数据库支持它),如下所示
def validate_other_booking_overlap
left = Arel::Nodes::Grouping.new(
[Booking.arel_table[:start_date],
Booking.arel_table[:end_date]])
right = Arel::Nodes::Grouping.new(
[Arel::Nodes::UnaryOperation.new(
'DATE',
[Arel::Nodes.build_quoted(start_date - 1)]),
Arel::Nodes::UnaryOperation.new(
'DATE',
[Arel::Nodes.build_quoted(end_date + 1)])])
condition = Arel::Nodes::InfixOperation.new('OVERLAPS', left, right)
errors.add(:overlaps_with_other) if Booking.where.not(id: id).where(condition).exists?
end