time_range_uniqueness 1.0.0 → 1.0.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: 868b4a177acdae3b835b05cb3b7d18741298b99592cdfb429dd38196287dc9f4
4
- data.tar.gz: 3b2a570fe65b0a08024bd730e1743a15966d0c9bfd2c674d5533e70014a27d69
3
+ metadata.gz: 1856dd82ab1e6aaddf8cd3c332ecbe84250218b2fae914426eba695d377c8513
4
+ data.tar.gz: f3d66c621fc3089986f9452bfcc02dedadd47c6b75fc914cc8b127af58dde021
5
5
  SHA512:
6
- metadata.gz: 8c01bcca608fc1341a154f3f7b40293f483ca27814fdfac96139edf390eac43b3d791c2b1aa8d9004d17597a91db06bfdd6205030c46d114abdddb2fd441d5ac
7
- data.tar.gz: 5b31ad1001851747cb211231329bdab526750558482d152eaa4fbe4217ef7afc637287d14a6235f1d5d6b8dbdee85ce2286f6dd1babf1bdaa37cb01c29f5e8e5
6
+ metadata.gz: 60e9c6b37fa3f0cc8ae51903006a182970900b9e17b37afcd4a7ff57fb84f58d7d84c7657eb661da6055e81ed07328941b2ee885c2c6e74e3fbccb942ae8528e
7
+ data.tar.gz: b2a21bd9c93329bf320ebe9f3b36f79912ab41adfd9a9bbef057d98f1bedbf700a5e058dfbb9ac08c01b3721def1cd71589af17850e9971524954cecef12ba83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.1] - 2026-05-31
4
+
5
+ - Fix the overlap validation to treat a `NULL` scope value as never-conflicting, matching
6
+ the exclusion constraint (`NULL = NULL` is never true in PostgreSQL). Previously the
7
+ validation reported a false overlap for rows the database would accept.
8
+ - Support models with a composite primary key in the overlap validation. Previously the
9
+ validation raised `ArgumentError` when excluding the current record, so such models
10
+ could not be validated or created.
11
+ - Raise `ArgumentError` when a custom `:name` exceeds PostgreSQL's 63-character identifier
12
+ limit, instead of relying on the database to truncate it silently.
13
+
3
14
  ## [1.0.0] - 2026-05-31
4
15
 
5
16
  - Require Ruby >= 3.2 and support ActiveRecord 7.1 through 8.x (and pg >= 1.5).
@@ -47,7 +47,7 @@ module TimeRangeUniqueness
47
47
  # @option options [Array<Symbol>] :scope (Optional) Columns to scope the uniqueness check.
48
48
  # @option options [String] :name (Optional) The name of the constraint.
49
49
  def add_time_range_uniqueness(table, options = {})
50
- raise ArgumentError, 'You must specify the :with option with the time range column name' unless options[:with]
50
+ validate_options!(options)
51
51
 
52
52
  time_range_column = options[:with]
53
53
  scope_columns = Array(options[:scope])
@@ -61,6 +61,19 @@ module TimeRangeUniqueness
61
61
 
62
62
  private
63
63
 
64
+ # Validates the options passed to +add_time_range_uniqueness+.
65
+ #
66
+ # @param options [Hash] The options for the constraint.
67
+ # @raise [ArgumentError] If +:with+ is missing or +:name+ exceeds the identifier limit.
68
+ def validate_options!(options)
69
+ raise ArgumentError, 'You must specify the :with option with the time range column name' unless options[:with]
70
+
71
+ name = options[:name]
72
+ return unless name && name.to_s.length > MAX_IDENTIFIER_LENGTH
73
+
74
+ raise ArgumentError, "The :name option exceeds the #{MAX_IDENTIFIER_LENGTH}-character identifier limit"
75
+ end
76
+
64
77
  # Applies the changes for the up migration.
65
78
  #
66
79
  # @param table [Symbol, String] The name of the table.
@@ -70,15 +70,16 @@ module TimeRangeUniqueness
70
70
  time_range = record.public_send(time_range_column)
71
71
  return false if time_range.nil?
72
72
 
73
+ # A NULL scope value can never satisfy the exclusion constraint's `=` comparison,
74
+ # so such a record can never conflict at the database level. Mirror that here.
75
+ return false if scope_columns.any? { |col| record.public_send(col).nil? }
76
+
73
77
  column = record.class.connection.quote_column_name(time_range_column)
74
78
  bounds = time_range.exclude_end? ? '[)' : '[]'
75
79
 
76
- scoped_relation(record, scope_columns).where(
77
- "#{column} && tstzrange(?, ?, ?)",
78
- time_range.begin,
79
- time_range.end,
80
- bounds
81
- ).exists?
80
+ scoped_relation(record, scope_columns)
81
+ .where("#{column} && tstzrange(?, ?, ?)", time_range.begin, time_range.end, bounds)
82
+ .exists?
82
83
  end
83
84
 
84
85
  # Builds the set of other records to check against, optionally scoped by the given columns.
@@ -88,7 +89,10 @@ module TimeRangeUniqueness
88
89
  # @return [ActiveRecord::Relation] All other records, scoped by the given columns.
89
90
  def self.scoped_relation(record, scope_columns)
90
91
  klass = record.class
91
- relation = klass.where.not(klass.primary_key => record.id)
92
+ # Pair each primary key column with its value so this works for both single
93
+ # and composite primary keys (record.id is an array for composite keys).
94
+ excluded = Array(klass.primary_key).zip(Array(record.id)).to_h
95
+ relation = klass.where.not(excluded)
92
96
 
93
97
  scope_columns.each do |col|
94
98
  relation = relation.where(col => record.public_send(col))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TimeRangeUniqueness
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: time_range_uniqueness
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - j-boers-13