Techioz Blog

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