Rails の 2 つのドット演算子とは何ですか?
概要
レールにおけるこれら 2 つのドットの意味は何ですか?
には次のような機能があります。
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 における包含的な有限範囲のリテラル構文です。
範囲は、両端を含む 2 つのドット (..)、または両端を含む 3 つのドット (…) で表現できます。排他的範囲には最後の要素は含まれません。
2.6 より前では、すべての Range は有限でした (構文的には開始と終了が必要ですが、終了は Float::INFINITY [開始の逆に -Float::INFINITY] にすることもできるため、技術的にはすべての Range が「有限」ではありませんでした)。
2.6 では、「無限範囲」は構文 x… を使用して実装されました。
2.7 以降、構文 ..x を使用して範囲を「先頭なし」にすることができます。
ドキュメントによれば、始まりのない範囲と無限の範囲は両方とも「半無限」であり、包括的または排他的として表現できます。 (正直に言うと、排他的な無限範囲がどのようなものになるのかはわかりません。ただし、構文には創造的な用途があります [下記を参照])
補足: (TL;DR)
すべての予約をループすることは、パフォーマンスの観点からは悪い考えです。
より良いアイデアは、次のようなものを使用してこれをデータベースにオフロードすることです。
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) は範囲を次のように扱うようになったからです。
…そして今、私たちは一周したと信じています。
アップデート
@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