Range on Rails


Notes on Umeda Tomohiro’s KaigiOnRails 2025 talk - Range on Rails: How PostgreSQL Multirange Simplifies Complex Booking Logic

In July I spent some time digging through past KaigiOnRails schedules while working on a CFP application. I found this talk by Umeda Tomohiro of Rizap Technologies. His talk focused on how to build a reservation system without fixed-length time slots. I was interested in understanding the approach as I’d seen the same problem solved differently by colleagues.

My highlights were: calculating availability by subtracting unavailable time ranges; understanding how to apply PostgreSQL’s multirange feature; and learning how to implement this maintainably in Rails.

Below are my notes from his talk. I used YouTube’s translation features and Google Translate for screenshots of some slides.

Umeda Tomohiro - 梅田智大
Links:

The new “multi-range type” option dramatically simplifies complex logic. chocoZAP - the story of creating a reservation system

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=3
How a general reservation system works: (1) Set aside a fixed reservation slot (2) User selects a slot and makes a reservation

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=4

Features of chocoZAP’s reservation system

In other words… a reservation system that isn’t bound by time slots!

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=5

The difficulty of a reservation system without available slots - Translated using Google Translate to preserve flowchart structure.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=6

Even if there are no available slots, I’d like to see a list of available slots. Since there is no physical “slot” system, creating a list of available slots is extremely difficult.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=7

The breakthrough is “scope”. By treating the periods during which reservations are unavailable as a set of ranges and calculating the difference between these ranges and the target period, we can determine whether a reservation is possible.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=8

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=9

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=10

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=11
What does it mean to consider something as a “set of ranges”?

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=12

The period during which reservations cannot be made is defined as the “range” - shown here as circles outlined with dotted lines.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=13

The period covered is also a scope - shown here as the shaded green circle marked “target period”

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=14
“Find the set difference of the ranges” - between shaded in blue the Period when reservations cannot be made, and in green the 1 week period covered.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=15
Results of finding the “set difference” - in the remaining range, reservations are open!

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=16

PostgreSQL’s Range Type cannot handle fragmented ranges.

Example: Subtract “12:10~12:40” from “12:00~13:00”.

The results are divided into “12:00~12:10” and “12:40~13:00”.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=17
The optimal solution is a “multi-range type”
That’s where the idea of multirange comes in. Multiple fragmented ranges can be treated as a single data point.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=18

What is a “multi-range type” in PostgreSQL?

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=19
Multiple range types can use the same operations as range types. Just like with range types, you can handle overlap, intersection and difference!

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=20

Create a list of available reservations using the “multi-range type”
1 Consolidate multiple periods that are unavailable for booking into multiple timeframes.
2 The period for checking availability is also converted to a multi-range
3 Calculate the available reservation period using the set different method

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=21
Consolidate multiple periods that are unavailable for booking into multiple timeframes.

Step 1

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=22
The period for checking availability has also been converted to a multi-range scope.
Since the set difference operation is only possible between multiple ranges, the time period to check availability is also converted to a multiple range.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=23
Calculate the available reservation period using the set difference method!

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=24
The “mutli-range type” has a variety of applications

When aggregating data across multiple disparate time periods, the multirange approach may prove very useful.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=25

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=26
Multi-range types are easier to handle when expected
A “multi-range type” is a data type that groups multiple ranges together.
It becomes easier to handle when expanded into individual areas.

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=27
Use PostgreSQL’s convenient functions to convert ranges!
Range_agg: “Combine multiple ranges into a single multi-range range”
Unnest: “Expanding one multi-range to ‘multiple ranges’”

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=28
Multiple ranges are best used exclusively for aggregation!
Each record is stored as a range
Aggregation into multiple range types during aggregation
Expanding the result and treating it as a range type simplifies the code

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=29

Simplify your code using SQL View

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=30
Example of aggregation using VIEW
Group unavailable periods into multirange - Aggregate available periods using the difference set - Expand multirange into range
Key

  1. Consolidate the periods when reservations cannot be made into a single table using UNION
    1. Existing reservation
    2. Room maintenance
    3. Store closure
  2. Group unavailable items into “multirange” using range_agg
  3. 1-week range - Unavailable range = Available range
  4. Unnest the multirange to create individual free periods (tsrange)

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=31
Creating a Model that corresponds to the View simplifies the code!
3) How to use
Room.reservation_availabilities => List of available reservation periods

https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=32
Summary