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..yattribute BETWEEN x AND y ( e.g. attribute >= x AND attribute <= y )x...yattribute >= x AND attribute < yx..attribute >= xx...attribute > x..xattribute <= x...xattribute < 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)
