Home > Enterprise >  What is the two dot operator in Rails?
What is the two dot operator in Rails?

Time:01-24

WHAT DOES THOSE TWO DOT MEAN.. in rails

has a function like this:

def period
   start_date..end_date
end

what is it? does this generate an array? I saw this being used in this class

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

CodePudding user response:

That is literal syntax for an inclusive finite Range in ruby.

A Range represents an interval—a set of values with a beginning and an end...When used as an iterator, ranges return each value in the sequence.

Ranges can be expressed as inclusive, two dots (..), or exclusive, three dots (...). Exclusive Ranges don't include the last element.

Before 2.6 all Ranges were finite (syntactically required a beginning and an end; although the end could be Float::INFINITY [inversely -Float::INFINITY for beginning] so technically not all Ranges were "finite").

In 2.6 "endless ranges" were implemented using the syntax x...

As of 2.7 Ranges can be "beginless" using the syntax ..x

Both beginless and endless Ranges are "semi-infinite" according to the documentation and can be expressed as either inclusive or exclusive. (Being honest I am not sure what an exclusive endless Range would be; although the syntax has creative uses [see below])

As a sidenote: (TL;DR)

Looping through every Booking is a bad idea from a performance standpoint.

A better idea would be to offload this to the database using something like:

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) converts finite Ranges in a WHERE condition to a SQL BETWEEN clause, so this query condition would be:

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

In newer versions of rails (>=6.0.3 and ruby >= 2.7) this:

Booking.arel_table[:start_date].gt(start_date)
  .and(Booking.arel_table[:end_date].lt(end_date))

Can be substituted with this

Booking.where(start_date: start_date..., end_date:...end_date))

because now Rails (Arel) treats Ranges as follows:

  • x..y attribute BETWEEN x AND y ( e.g. attribute >= x AND attribute <= y )
  • x...y attribute >= x AND attribute < y
  • x.. attribute >= x
  • x... attribute > x
  • ..x attribute <= x
  • ...x attribute < x

...And now I believe we've come full circle.

UPDATE

Due to @max's comments you could use the OVERLAPS function (where the database supports it) as follows

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

This is untested and written on a phone from memory and understanding (results not guaranteed)

  •  Tags:  
  • Related