activerecord-multirange 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f84a86677dd7f4427b636524b7dc09ea1c5eb049db16ebc727e86b8801847245
4
- data.tar.gz: e7d313979f6e205f7cc07685b6fad56d6e483e99ae8cf985eb04c5fd0c2a3acb
3
+ metadata.gz: 0b09cdb0717d19e40572d66ecb2e7774494057658fb3409fefdf04a67db06345
4
+ data.tar.gz: aa643ce5bf82da7e69b042478ee884668fd1970da0861b91e43e7a5c2e4ae3e4
5
5
  SHA512:
6
- metadata.gz: 80c306db18783b1f27457706731efa67594c06ac8a7efd7fbcf5e368befbe2166ba136a676026fa6bab076fe191de1563b36cdaab3690a3b01bf9ca0d97007a3
7
- data.tar.gz: 6eb784fa47af4ad1fb395d0e5f4dc56bb9e2a8f2a48b85d24bf036e5bcebc93ed128d9a9f24d75144eb05f285089d8b47e27820571e6b4cece2b43ca33f9af41
6
+ metadata.gz: 31209bbe807b481f93275650fae69c2c23fa76518fc987890daec2c619f595571467cb55fdd8cc526e8bc4e007af68eb9f239e68d810854dc3e6977c4631d248
7
+ data.tar.gz: 87c60c9246d43e7b148c02798171df9747b7aa2fb5c21b40969ed77b8461064440d4f9f73840f2b5d8aa11cd47d33017d48fbfb9a9c9ad28da7ce952e713fa26
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [1.1.0] - 2024-12-19
3
+ ## [1.1.1] - 2025-06-11
4
+
5
+ - Added automatic registration of multirange types in `NATIVE_DATABASE_TYPES` for Rails 8 compatibility
6
+ - Fixed `rails db:schema:dump` support without requiring manual type registration
7
+ - Centralized multirange type definitions in `MULTIRANGE_TYPES` constant
8
+
9
+ ## [1.1.0] - 2024-06-01
4
10
 
5
11
  - Minor version bump with improvements and updates
6
12
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-multirange (1.1.0)
4
+ activerecord-multirange (1.1.1)
5
5
  pg (>= 1)
6
6
  rails (>= 6)
7
7
 
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- # Activercord Multirange
2
-
1
+ # Activercord Multirange
2
+
3
3
  This gem adds full suppport of [Postgress Multiranges](https://www.postgresql.org/docs/14/rangetypes.html#RANGETYPES-BUILTIN) types.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/activerecord-multirange.svg)](https://badge.fury.io/rb/activerecord-multirange)
@@ -12,30 +12,233 @@ Install the gem and add to the application's Gemfile by executing:
12
12
 
13
13
  If bundler is not being used to manage dependencies, install the gem by executing:
14
14
 
15
- $ gem install activerecord-multirange
15
+ $ gem install activerecord-multirange
16
16
 
17
17
  ## Usage
18
18
 
19
19
  ### Initialize it
20
20
 
21
- ```
22
- # config/initializers/activerecord_multirange
21
+ ```ruby
22
+ # config/initializers/activerecord_multirange.rb
23
23
 
24
24
  Activerecord::Multirange.add_multirange_column_type
25
25
  ```
26
26
 
27
27
  ### Migrations
28
28
 
29
- All multirange types are available on the migrations
29
+ All multirange types are available in migrations. Here are examples for different multirange types:
30
+
31
+ ```ruby
32
+ class CreateSchedules < ActiveRecord::Migration[7.0]
33
+ def change
34
+ create_table :schedules do |t|
35
+ t.string :name
36
+ t.tsmultirange :available_times # Timestamp multirange
37
+ t.tstzmultirange :available_times_tz # Timestamp with timezone multirange
38
+ t.datemultirange :available_dates # Date multirange
39
+ t.nummultirange :price_ranges # Numeric multirange
40
+ t.int8multirange :id_ranges # Bigint multirange
41
+ t.int4multirange :quantity_ranges # Integer multirange
42
+
43
+ t.timestamps
44
+ end
45
+ end
46
+ end
47
+ ```
48
+
49
+ ```ruby
50
+ class CreateBookings < ActiveRecord::Migration[7.0]
51
+ def change
52
+ create_table :bookings do |t|
53
+ t.string :title
54
+ t.tstzmultirange :booked_periods
55
+ t.datemultirange :blackout_dates
56
+
57
+ t.timestamps
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+ ### Models
64
+
65
+ Define your models to work with multirange columns:
66
+
67
+ ```ruby
68
+ class Schedule < ApplicationRecord
69
+ # Multirange columns are automatically handled by ActiveRecord
70
+ # No special configuration needed
71
+ end
72
+
73
+ class Booking < ApplicationRecord
74
+ validates :title, presence: true
75
+
76
+ scope :overlapping_with, ->(time_range) { where('booked_periods && ?', time_range) }
77
+ end
78
+ ```
79
+
80
+ ### Creating and Working with Multirange Data
81
+
82
+ #### Creating Records with Multirange Values
83
+
84
+ ```ruby
85
+ # Using timestamp multiranges for scheduling
86
+ schedule =
87
+ Schedule.create!(
88
+ name: 'Conference Room A',
89
+ available_times: [
90
+ Time.parse('2024-01-15 09:00')..Time.parse('2024-01-15 12:00'),
91
+ Time.parse('2024-01-15 14:00')..Time.parse('2024-01-15 17:00')
92
+ ]
93
+ )
94
+
95
+ # Using date multiranges for availability periods
96
+ booking =
97
+ Booking.create!(
98
+ title: 'Annual Maintenance',
99
+ booked_periods: [
100
+ Time.zone.parse('2024-03-01 00:00')..Time.zone.parse('2024-03-03 23:59'),
101
+ Time.zone.parse('2024-06-15 00:00')..Time.zone.parse('2024-06-17 23:59')
102
+ ],
103
+ blackout_dates: [
104
+ Date.parse('2024-12-24')..Date.parse('2024-12-26'),
105
+ Date.parse('2024-12-31')..Date.parse('2024-01-01')
106
+ ]
107
+ )
108
+
109
+ # Using numeric multiranges for pricing tiers
110
+ product = Product.create!(name: 'Premium Service', price_ranges: [10.0..50.0, 100.0..500.0, 1000.0..5000.0])
111
+ ```
112
+
113
+ #### Reading and Manipulating Multirange Data
114
+
115
+ ```ruby
116
+ schedule = Schedule.find(1)
117
+
118
+ # Access multirange values
119
+ puts schedule.available_times
120
+ # => [2024-01-15 09:00:00 UTC..2024-01-15 12:00:00 UTC, 2024-01-15 14:00:00 UTC..2024-01-15 17:00:00 UTC]
121
+
122
+ # Check if multirange contains a specific value
123
+ morning_slot = Time.parse('2024-01-15 10:30')
124
+ puts schedule.available_times.any? { |range| range.cover?(morning_slot) }
125
+ # => true
126
+
127
+ # Add new time ranges
128
+ schedule.available_times += [Time.parse('2024-01-15 18:00')..Time.parse('2024-01-15 20:00')]
129
+ schedule.save!
30
130
 
131
+ # Working with individual ranges
132
+ schedule.available_times.each { |time_range| puts "Available from #{time_range.begin} to #{time_range.end}" }
133
+ ```
134
+
135
+ ### Querying Multirange Columns
136
+
137
+ #### Overlap Queries
138
+
139
+ ```ruby
140
+ # Find schedules that overlap with a specific time range
141
+ search_range = Time.parse('2024-01-15 10:00')..Time.parse('2024-01-15 11:00')
142
+ overlapping_schedules = Schedule.where('available_times && ?', search_range)
143
+
144
+ # Find bookings that don't overlap with a date range
145
+ available_dates = Date.parse('2024-03-01')..Date.parse('2024-03-05')
146
+ non_conflicting_bookings = Booking.where('NOT (blackout_dates && ?)', available_dates)
147
+ ```
148
+
149
+ #### Contains Queries
150
+
151
+ ```ruby
152
+ # Find schedules that contain a specific timestamp
153
+ specific_time = Time.parse('2024-01-15 10:30')
154
+ containing_schedules = Schedule.where('available_times @> ?', specific_time)
155
+
156
+ # Find products within a specific price range
157
+ price_point = 75.0
158
+ products_in_range = Product.where('price_ranges @> ?', price_point)
159
+ ```
160
+
161
+ #### Other Useful Queries
162
+
163
+ ```ruby
164
+ # Check if multirange is contained within another range
165
+ broad_range = Time.parse('2024-01-15 08:00')..Time.parse('2024-01-15 18:00')
166
+ fully_contained = Schedule.where('available_times <@ ?', broad_range)
167
+
168
+ # Find records where multiranges are strictly left of a range
169
+ cutoff_time = Time.parse('2024-01-15 12:00')..Time.parse('2024-01-15 24:00')
170
+ morning_only = Schedule.where('available_times << ?', cutoff_time)
31
171
 
172
+ # Find records where multiranges are strictly right of a range
173
+ start_time = Time.parse('2024-01-15 00:00')..Time.parse('2024-01-15 12:00')
174
+ afternoon_only = Schedule.where('available_times >> ?', start_time)
32
175
  ```
33
- t.tsmultirange :column
34
- t.tstzmultirange :column_tz
35
- t.datemultirange :column_date
36
- t.nummultirange :column_num
37
- t.int8multirange :column_int8
38
- t.int4multirange :column_int4
176
+
177
+ ### Practical Examples
178
+
179
+ #### Availability Scheduling System
180
+
181
+ ```ruby
182
+ class Room < ApplicationRecord
183
+ def available_during?(time_range)
184
+ available_times.any? { |range| range.cover?(time_range) }
185
+ end
186
+
187
+ def book_time!(time_range)
188
+ # Remove the booked time from available times
189
+ new_availability = []
190
+ available_times.each do |available_range|
191
+ if available_range.overlaps?(time_range)
192
+ # Split the range if needed
193
+ if available_range.begin < time_range.begin
194
+ new_availability << (available_range.begin...time_range.begin)
195
+ end
196
+ if time_range.end < available_range.end
197
+ new_availability << (time_range.end...available_range.end)
198
+ end
199
+ else
200
+ new_availability << available_range
201
+ end
202
+ end
203
+
204
+ update!(available_times: new_availability)
205
+ end
206
+ end
207
+
208
+ # Usage
209
+ room = Room.find(1)
210
+ booking_time = Time.parse('2024-01-15 10:00')..Time.parse('2024-01-15 11:00')
211
+
212
+ if room.available_during?(booking_time)
213
+ room.book_time!(booking_time)
214
+ puts 'Room booked successfully!'
215
+ else
216
+ puts 'Room not available during requested time'
217
+ end
218
+ ```
219
+
220
+ #### Price Range Management
221
+
222
+ ```ruby
223
+ class Product < ApplicationRecord
224
+ def price_tier_for(quantity)
225
+ price_ranges.each_with_index do |range, index|
226
+ if range.cover?(quantity)
227
+ return index + 1
228
+ end
229
+ end
230
+ nil
231
+ end
232
+
233
+ def applies_to_quantity?(quantity)
234
+ quantity_ranges.any? { |range| range.cover?(quantity) }
235
+ end
236
+ end
237
+
238
+ # Usage
239
+ product = Product.find(1)
240
+ puts "Quantity 25 is in tier: #{product.price_tier_for(25)}"
241
+ puts "Product applies to quantity 150: #{product.applies_to_quantity?(150)}"
39
242
  ```
40
243
 
41
244
  ## Development
@@ -9,28 +9,7 @@ module Activerecord
9
9
  end
10
10
 
11
11
  def self.native_database_types
12
- super.merge(
13
- {
14
- datemultirange: {
15
- name: 'datemultirange'
16
- },
17
- nummultirange: {
18
- name: 'nummultirange'
19
- },
20
- tsmultirange: {
21
- name: 'tsmultirange'
22
- },
23
- tstzmultirange: {
24
- name: 'tstzmultirange'
25
- },
26
- int4multirange: {
27
- name: 'int4multirange'
28
- },
29
- int8multirange: {
30
- name: 'int8multirange'
31
- }
32
- }
33
- )
12
+ super.merge(Activerecord::Multirange::MULTIRANGE_TYPES)
34
13
  end
35
14
 
36
15
  def load_multirange_types
@@ -4,14 +4,7 @@ module Activerecord
4
4
  module Multirange
5
5
  module SchemaStatements
6
6
  def native_database_types
7
- super.merge({
8
- tsmultirange: { name: "tsmultirange" },
9
- datemultirange: { name: "datemultirange" },
10
- tstzmultirange: { name: "tstzmultirange" },
11
- nummultirange: { name: "nummultirange" },
12
- int8multirange: { name: "int8multirange" },
13
- int4multirange: { name: "int4multirange" }
14
- })
7
+ super.merge(Activerecord::Multirange::MULTIRANGE_TYPES)
15
8
  end
16
9
  end
17
10
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Activerecord
4
4
  module Multirange
5
- VERSION = "1.1.0"
5
+ VERSION = '1.1.1'
6
6
  end
7
7
  end
@@ -13,8 +13,21 @@ module Activerecord
13
13
  class Error < StandardError
14
14
  end
15
15
 
16
+ # Multirange types that need to be registered
17
+ MULTIRANGE_TYPES = {
18
+ tsmultirange: { name: "tsmultirange" },
19
+ datemultirange: { name: "datemultirange" },
20
+ tstzmultirange: { name: "tstzmultirange" },
21
+ nummultirange: { name: "nummultirange" },
22
+ int8multirange: { name: "int8multirange" },
23
+ int4multirange: { name: "int4multirange" }
24
+ }.freeze
25
+
16
26
  def self.add_multirange_column_type
17
27
  ActiveSupport.on_load(:active_record) do
28
+ # Register multirange types in NATIVE_DATABASE_TYPES for Rails 8 compatibility
29
+ Activerecord::Multirange.register_native_database_types
30
+
18
31
  ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Adapter)
19
32
  ActiveRecord::ConnectionAdapters::PostgreSQL::OID::TypeMapInitializer
20
33
  .prepend(TypeMap)
@@ -27,5 +40,27 @@ module Activerecord
27
40
  )
28
41
  end
29
42
  end
43
+
44
+ def self.register_native_database_types
45
+ # Ensure the PostgreSQL adapter is loaded
46
+ return unless defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
47
+
48
+ adapter_class = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
49
+
50
+ # Check if NATIVE_DATABASE_TYPES is already defined and modifiable
51
+ if adapter_class.const_defined?(:NATIVE_DATABASE_TYPES)
52
+ current_types = adapter_class::NATIVE_DATABASE_TYPES
53
+
54
+ # Only modify if our types aren't already registered
55
+ unless current_types.key?(:tsmultirange)
56
+ # Create a new hash with existing types plus our multirange types
57
+ new_types = current_types.merge(MULTIRANGE_TYPES)
58
+
59
+ # Replace the constant with the updated hash
60
+ adapter_class.send(:remove_const, :NATIVE_DATABASE_TYPES)
61
+ adapter_class.const_set(:NATIVE_DATABASE_TYPES, new_types.freeze)
62
+ end
63
+ end
64
+ end
30
65
  end
31
66
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-multirange
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gustavo Warmling Teixeira
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-03 00:00:00.000000000 Z
10
+ date: 2025-06-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: pg