time_range_uniqueness 0.1.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: 5d6fd29dd2746a4129bb39f795c097fe4052eb0fa7dbf9d8c6398d8c35ff4cb4
4
- data.tar.gz: 9b58e702e5797b25b633eda09e77c9fd0090ba1313de7d1c25453a54584d1ff3
3
+ metadata.gz: 1856dd82ab1e6aaddf8cd3c332ecbe84250218b2fae914426eba695d377c8513
4
+ data.tar.gz: f3d66c621fc3089986f9452bfcc02dedadd47c6b75fc914cc8b127af58dde021
5
5
  SHA512:
6
- metadata.gz: 85d7de7b1f1a78f47bad178fdba8d2b27c39c4322cfc528e92436985c5f451488b9a92c2a14c6945b5b5d31315f940fad718091c73958ec98ec3696a79f9c7f5
7
- data.tar.gz: c62fd7916b188a288e0d5ed8c0d5c97b7bad16811173ff11614a6302b3809a90eaf2293ac5677abb5a8b269114f1897f9d73082f38403877a0d6b909227474e3
6
+ metadata.gz: 60e9c6b37fa3f0cc8ae51903006a182970900b9e17b37afcd4a7ff57fb84f58d7d84c7657eb661da6055e81ed07328941b2ee885c2c6e74e3fbccb942ae8528e
7
+ data.tar.gz: b2a21bd9c93329bf320ebe9f3b36f79912ab41adfd9a9bbef057d98f1bedbf700a5e058dfbb9ac08c01b3721def1cd71589af17850e9971524954cecef12ba83
data/.rubocop.yml CHANGED
@@ -1,7 +1,24 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-performance
3
3
  - rubocop-rspec
4
4
 
5
5
  AllCops:
6
- TargetRubyVersion: 2.6
7
- NewCops: enable
6
+ TargetRubyVersion: 3.2
7
+ NewCops: enable
8
+ SuggestExtensions: false
9
+
10
+ Style/Documentation:
11
+ inherit_mode:
12
+ merge:
13
+ - Exclude
14
+ Exclude:
15
+ - lib/time_range_uniqueness.rb
16
+
17
+ RSpec/BeforeAfterAll:
18
+ Enabled: false
19
+
20
+ RSpec/MultipleMemoizedHelpers:
21
+ Max: 8
22
+
23
+ RSpec/ExampleLength:
24
+ Max: 8
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
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
+
14
+ ## [1.0.0] - 2026-05-31
15
+
16
+ - Require Ruby >= 3.2 and support ActiveRecord 7.1 through 8.x (and pg >= 1.5).
17
+ - Fix the overlap validation to honor the time range's bound inclusivity (`..` vs `...`)
18
+ so it agrees with the PostgreSQL exclusion constraint. Previously a record whose
19
+ inclusive endpoint touched an existing record passed validation but was rejected by
20
+ the database with an unhandled `ExclusionViolation`.
21
+ - Use the model's primary key instead of assuming a column named `id` when excluding the
22
+ current record from the overlap check.
23
+ - The model validation is now only extended onto `ActiveRecord::Base` (rather than also
24
+ being included), so its helpers no longer leak onto every record instance.
25
+ - **Breaking:** `add_time_range_uniqueness` now raises `ArgumentError` when the `:with`
26
+ option is omitted, matching `validates_time_range_uniqueness`.
27
+ - Generated constraint names are kept within PostgreSQL's 63-character identifier limit
28
+ (long names are truncated with a digest suffix to stay deterministic and collision-free).
29
+ - Quote table and column identifiers in the generated migration and validation SQL.
30
+
3
31
  ## [0.1.0] - 2024-09-15
4
32
 
5
33
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
- # TimeRangeUniqueness
1
+ # time_range_uniqueness
2
2
 
3
- **TimeRangeUniqueness** is a Ruby gem that provides ActiveRecord migrations and model validation to ensure that time ranges do not overlap within a table. It adds support for creating exclusion constraints on PostgreSQL `tstzrange` columns and validates the uniqueness of time ranges in models.
3
+ **time_range_uniqueness** is a Ruby gem that provides ActiveRecord migrations and model validation to ensure that time ranges do not overlap within a table.
4
+
5
+ It adds support for creating exclusion constraints on PostgreSQL `tstzrange` columns and validates the uniqueness of time ranges in models.
6
+
7
+ [![rspec](https://github.com/j-boers-13/time_range_uniqueness/actions/workflows/ci.yml/badge.svg)](https://github.com/j-boers-13/time_range_uniqueness/actions/workflows/ci.yml)
4
8
 
5
9
  ## Features
6
10
 
@@ -34,29 +38,20 @@ gem install time_range_uniqueness
34
38
 
35
39
  In your migrations, you can use the `add_time_range_uniqueness` method to add a time range column with an exclusion constraint. This will prevent overlapping time ranges in your table.
36
40
 
37
- ```ruby
38
- class AddEventTimeRangeUniqueness < ActiveRecord::Migration[6.1]
39
- def change
40
- add_time_range_uniqueness :events,
41
- with: :event_time_range,
42
- scope: :event_name, # Optional scope
43
- name: 'unique_event_time_ranges' # Optional custom constraint name
44
- end
45
- end
46
- ```
47
-
48
41
  #### Options:
49
42
  - `with`: **(Required)** The name of the column that stores the time range.
50
43
  - `scope`: **(Optional)** An array of columns to scope the uniqueness check (e.g., `:event_name`).
51
44
  - `name`: **(Optional)** The name of the exclusion constraint. If not provided, a default name is generated.
52
- - `column_type`: The time range column type will always be `tstzrange`.
53
45
 
54
46
  ### Example
55
47
 
56
48
  ```ruby
57
49
  class AddEventTimeRangeUniqueness < ActiveRecord::Migration[6.1]
58
50
  def change
59
- add_time_range_uniqueness :events, with: :event_time_range, scope: :event_name
51
+ add_time_range_uniqueness :events,
52
+ with: :event_time_range,
53
+ scope: :event_name, # Optional scope
54
+ name: 'unique_event_time_ranges' # Optional custom constraint name
60
55
  end
61
56
  end
62
57
  ```
@@ -67,30 +62,24 @@ This example ensures that the `event_time_range` column in the `events` table is
67
62
 
68
63
  The gem also provides model-level validation to ensure time ranges do not overlap. You can include this validation in your models like this:
69
64
 
70
- ```ruby
71
- class Event < ApplicationRecord
72
- validates_time_range_uniqueness(
73
- with: :event_time_range,
74
- scope: :event_name,
75
- message: 'cannot overlap with an existing event'
76
- )
77
- end
78
- ```
79
-
80
65
  #### Options:
81
66
  - `with`: **(Required)** The name of the time range column to validate.
82
67
  - `scope`: **(Optional)** An array of columns to scope the uniqueness check (e.g., `:event_name`).
83
68
  - `message`: **(Optional)** A custom error message when validation fails (default: `'overlaps with an existing record'`).
84
69
 
85
- ### Example
70
+ #### Examples:
86
71
 
87
72
  ```ruby
88
- class Event < ApplicationRecord
89
- validates_time_range_uniqueness with: :event_time_range, scope: :event_name
73
+ class Event < ActiveRecord::Base
74
+ validates_time_range_uniqueness(
75
+ with: :event_time_range,
76
+ scope: :event_name,
77
+ message: 'cannot overlap with an existing event'
78
+ )
90
79
  end
91
80
  ```
92
81
 
93
- This example ensures that the `event_time_range` in the `Event` model does not overlap with other events with the same `event_name`.
82
+ This example ensures that the `event_time_range` in the `Event` model does not overlap with other events with the same `event_name` and will display the message `cannot overlap with an existing event` when it does.
94
83
 
95
84
  ### PostgreSQL Requirements
96
85
 
@@ -102,8 +91,8 @@ CREATE EXTENSION IF NOT EXISTS btree_gist;
102
91
 
103
92
  ## Contributing
104
93
 
105
- Bug reports and pull requests are welcome on GitHub at [https://github.com/your_username/time_range_uniqueness](https://github.com/your_username/time_range_uniqueness).
94
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/j-boers-13/time_range_uniqueness](https://github.com/j-boers-13/time_range_uniqueness)
106
95
 
107
96
  ## License
108
97
 
109
- The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
98
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -7,9 +7,6 @@ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  require 'rubocop/rake_task'
9
9
 
10
- RuboCop::RakeTask.new do |task|
11
- task.requires << 'rubocop-performance'
12
- task.requires << 'rubocop-rspec'
13
- end
10
+ RuboCop::RakeTask.new
14
11
 
15
12
  task default: %i[spec rubocop]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
4
+
3
5
  module TimeRangeUniqueness
4
6
  # This module provides methods for adding and managing time range uniqueness
5
7
  # constraints in ActiveRecord migrations.
@@ -14,7 +16,6 @@ module TimeRangeUniqueness
14
16
  # add_time_range_uniqueness :events,
15
17
  # with: :event_time_range,
16
18
  # scope: :event_name,
17
- # column_type: :tstzrange,
18
19
  # name: 'unique_event_time_ranges'
19
20
  # end
20
21
  # end
@@ -23,7 +24,6 @@ module TimeRangeUniqueness
23
24
  #
24
25
  # * +:with+ - The name of the column that stores the time range (required).
25
26
  # * +:scope+ - (Optional) An array of columns to scope the uniqueness check.
26
- # * +:column_type+ - (Optional) The type of the time range column (default: :tstzrange).
27
27
  # * +:name+ - (Optional) The name of the constraint.
28
28
  #
29
29
  # == Methods
@@ -31,6 +31,11 @@ module TimeRangeUniqueness
31
31
  # * +add_time_range_uniqueness(table, options = {})+ - Adds the time range column and the exclusion constraint.
32
32
  # * +CommandRecorder+ - Records the `add_time_range_uniqueness` command so it can be replayed during rollback.
33
33
  module MigrationAdditions
34
+ COLUMN_TYPE = :tstzrange
35
+
36
+ # PostgreSQL truncates identifiers to 63 bytes (NAMEDATALEN - 1).
37
+ MAX_IDENTIFIER_LENGTH = 63
38
+
34
39
  # Adds a time range column and an exclusion constraint to the specified table.
35
40
  #
36
41
  # This method creates or modifies a column to store time ranges and ensures that
@@ -40,33 +45,45 @@ module TimeRangeUniqueness
40
45
  # @param options [Hash] The options for the constraint.
41
46
  # @option options [Symbol] :with The name of the time range column.
42
47
  # @option options [Array<Symbol>] :scope (Optional) Columns to scope the uniqueness check.
43
- # @option options [Symbol] :column_type (Optional) The type of the time range column (default: :tstzrange).
44
48
  # @option options [String] :name (Optional) The name of the constraint.
45
49
  def add_time_range_uniqueness(table, options = {})
46
- time_range_column = options[:with] || :time_range
50
+ validate_options!(options)
51
+
52
+ time_range_column = options[:with]
47
53
  scope_columns = Array(options[:scope])
48
- column_type = :tstzrange
49
54
  constraint_name = options[:name] || generate_constraint_name(table, scope_columns, time_range_column)
50
55
 
51
56
  reversible do |dir|
52
- dir.up { apply_up_migration(table, time_range_column, column_type, options, constraint_name, scope_columns) }
57
+ dir.up { apply_up_migration(table, time_range_column, options, constraint_name, scope_columns) }
53
58
  dir.down { apply_down_migration(table, time_range_column, constraint_name) }
54
59
  end
55
60
  end
56
61
 
57
62
  private
58
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
+
59
77
  # Applies the changes for the up migration.
60
78
  #
61
79
  # @param table [Symbol, String] The name of the table.
62
80
  # @param time_range_column [Symbol] The time range column name.
63
- # @param column_type [Symbol] The type of the column.
64
81
  # @param options [Hash] Additional options for the column.
65
82
  # @param constraint_name [String] The name of the constraint.
66
83
  # @param scope_columns [Array<Symbol>] The columns used in the scope.
67
- def apply_up_migration(table, time_range_column, column_type, options, constraint_name, scope_columns)
84
+ def apply_up_migration(table, time_range_column, options, constraint_name, scope_columns)
68
85
  setup_extension
69
- add_column_to_table(table, time_range_column, column_type, options)
86
+ add_column_to_table(table, time_range_column, options)
70
87
  add_exclusion_constraint(table, constraint_name, scope_columns, time_range_column)
71
88
  end
72
89
 
@@ -87,7 +104,13 @@ module TimeRangeUniqueness
87
104
  # @param time_range_column [Symbol] The time range column name.
88
105
  # @return [String] The generated constraint name.
89
106
  def generate_constraint_name(table, scope_columns, time_range_column)
90
- "exclude_#{table}_on_#{[scope_columns, time_range_column].flatten.join('_')}"
107
+ name = "exclude_#{table}_on_#{[scope_columns, time_range_column].flatten.join('_')}"
108
+ return name if name.length <= MAX_IDENTIFIER_LENGTH
109
+
110
+ # Keep the name deterministic and within PostgreSQL's limit so the up and down
111
+ # migrations refer to the same constraint. A digest avoids collisions after truncation.
112
+ digest = Digest::SHA256.hexdigest(name)[0, 10]
113
+ "#{name[0, MAX_IDENTIFIER_LENGTH - digest.length - 1]}_#{digest}"
91
114
  end
92
115
 
93
116
  # Ensures the btree_gist extension is enabled.
@@ -99,12 +122,11 @@ module TimeRangeUniqueness
99
122
  #
100
123
  # @param table [Symbol, String] The name of the table.
101
124
  # @param time_range_column [Symbol] The time range column name.
102
- # @param column_type [Symbol] The type of the column.
103
125
  # @param options [Hash] Additional options for the column.
104
- def add_column_to_table(table, time_range_column, column_type, options)
126
+ def add_column_to_table(table, time_range_column, options)
105
127
  return if column_exists?(table, time_range_column)
106
128
 
107
- add_column table, time_range_column, column_type, **options.slice(:null, :default)
129
+ add_column table, time_range_column, COLUMN_TYPE, **options.slice(:null, :default)
108
130
  end
109
131
 
110
132
  # Adds an exclusion constraint to the table.
@@ -114,13 +136,13 @@ module TimeRangeUniqueness
114
136
  # @param scope_columns [Array<Symbol>] The columns used in the scope.
115
137
  # @param time_range_column [Symbol] The time range column name.
116
138
  def add_exclusion_constraint(table, constraint_name, scope_columns, time_range_column)
117
- columns = scope_columns.map { |col| "#{col} WITH =" }
118
- columns << "#{time_range_column} WITH &&"
139
+ columns = scope_columns.map { |col| "#{quote_column_name(col)} WITH =" }
140
+ columns << "#{quote_column_name(time_range_column)} WITH &&"
119
141
  expression = columns.join(', ')
120
142
 
121
143
  execute <<-SQL
122
- ALTER TABLE #{table}
123
- ADD CONSTRAINT #{constraint_name}
144
+ ALTER TABLE #{quote_table_name(table)}
145
+ ADD CONSTRAINT #{quote_column_name(constraint_name)}
124
146
  EXCLUDE USING GIST (#{expression});
125
147
  SQL
126
148
  end
@@ -131,8 +153,8 @@ module TimeRangeUniqueness
131
153
  # @param constraint_name [String] The name of the constraint.
132
154
  def remove_exclusion_constraint(table, constraint_name)
133
155
  execute <<-SQL
134
- ALTER TABLE #{table}
135
- DROP CONSTRAINT IF EXISTS #{constraint_name};
156
+ ALTER TABLE #{quote_table_name(table)}
157
+ DROP CONSTRAINT IF EXISTS #{quote_column_name(constraint_name)};
136
158
  SQL
137
159
  end
138
160
 
@@ -4,8 +4,8 @@ module TimeRangeUniqueness
4
4
  # The `ModelAdditions` module provides a custom validation for ensuring that time ranges
5
5
  # in ActiveRecord models are unique across records, optionally scoped by other columns.
6
6
  #
7
- # This module is intended to be included in ActiveRecord models and used to add
8
- # validation methods to check for overlapping time ranges between records.
7
+ # This module is extended onto ActiveRecord::Base so that models gain a
8
+ # validation method to check for overlapping time ranges between records.
9
9
  #
10
10
  # == Example
11
11
  #
@@ -31,10 +31,10 @@ module TimeRangeUniqueness
31
31
  # == Methods
32
32
  #
33
33
  # * +validates_time_range_uniqueness+ - Adds a validation for time range uniqueness.
34
- # * +validate_records+ - Internal method to perform the validation.
35
- # * +time_range_column_overlapping?+ - Internal method to check for overlapping time ranges.
34
+ # * +ModelAdditions.overlapping?+ - Internal helper that checks for overlapping time ranges.
35
+ # * +ModelAdditions.scoped_relation+ - Internal helper that builds the scoped relation.
36
36
  #
37
- # When included in an ActiveRecord model, this module adds the ability to ensure that
37
+ # Extending this onto ActiveRecord::Base adds the ability to ensure that
38
38
  # the specified time range does not overlap with other records' time ranges, optionally
39
39
  # scoped by additional fields.
40
40
  module ModelAdditions
@@ -52,53 +52,53 @@ module TimeRangeUniqueness
52
52
 
53
53
  time_range_column = options[:with]
54
54
  scope_columns = Array(options[:scope])
55
+ message = options[:message] || 'overlaps with an existing record'
55
56
 
56
- validate_records(time_range_column, scope_columns, options)
57
+ validate do
58
+ overlapping = TimeRangeUniqueness::ModelAdditions.overlapping?(self, time_range_column, scope_columns)
59
+ errors.add(time_range_column, message) if overlapping
60
+ end
57
61
  end
58
62
 
59
- private
60
-
61
- # Defines the validation logic for ensuring time range uniqueness.
62
- #
63
- # This method is called internally by the validation and checks whether a record's
64
- # time range overlaps with any other records, optionally scoped by other columns.
63
+ # Checks whether the record's time range overlaps any other record, optionally scoped.
65
64
  #
65
+ # @param record [ActiveRecord::Base] The record being validated.
66
66
  # @param time_range_column [Symbol] The name of the time range column.
67
67
  # @param scope_columns [Array<Symbol>] The columns to scope the uniqueness check.
68
- # @param options [Hash] The options for the validation.
69
- def validate_records(time_range_column, scope_columns, options)
70
- validate do
71
- time_range = public_send(time_range_column)
68
+ # @return [Boolean] True if there is an overlap, false otherwise.
69
+ def self.overlapping?(record, time_range_column, scope_columns)
70
+ time_range = record.public_send(time_range_column)
71
+ return false if time_range.nil?
72
72
 
73
- next if time_range.nil?
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? }
74
76
 
75
- relation = self.class.where.not(id: id)
77
+ column = record.class.connection.quote_column_name(time_range_column)
78
+ bounds = time_range.exclude_end? ? '[)' : '[]'
76
79
 
77
- scope_columns.each do |col|
78
- relation = relation.where(col => public_send(col))
79
- end
80
+ scoped_relation(record, scope_columns)
81
+ .where("#{column} && tstzrange(?, ?, ?)", time_range.begin, time_range.end, bounds)
82
+ .exists?
83
+ end
80
84
 
81
- overlapping = time_range_column_overlapping?(relation, time_range_column, time_range)
85
+ # Builds the set of other records to check against, optionally scoped by the given columns.
86
+ #
87
+ # @param record [ActiveRecord::Base] The record being validated.
88
+ # @param scope_columns [Array<Symbol>] The columns to scope the uniqueness check.
89
+ # @return [ActiveRecord::Relation] All other records, scoped by the given columns.
90
+ def self.scoped_relation(record, scope_columns)
91
+ klass = record.class
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)
82
96
 
83
- errors.add(time_range_column, options[:message] || 'overlaps with an existing record') if overlapping
97
+ scope_columns.each do |col|
98
+ relation = relation.where(col => record.public_send(col))
84
99
  end
85
- end
86
100
 
87
- # Checks if the given time range overlaps with any existing records.
88
- #
89
- # This method performs the actual overlap check by querying the database using the
90
- # GiST index for range data types in PostgreSQL.
91
- #
92
- # @param relation [ActiveRecord::Relation] The scope of records to check against.
93
- # @param time_range_column [Symbol] The name of the time range column.
94
- # @param time_range [Range] The time range to check for overlap.
95
- # @return [Boolean] True if there is an overlap, false otherwise.
96
- def time_range_column_overlapping?(relation, time_range_column, time_range)
97
- relation.where(
98
- "#{time_range_column} && tstzrange(?, ?, '[)')",
99
- time_range.begin,
100
- time_range.end
101
- ).exists?
101
+ relation
102
102
  end
103
103
  end
104
104
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TimeRangeUniqueness
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.1'
5
5
  end
@@ -16,5 +16,4 @@ end
16
16
 
17
17
  ActiveSupport.on_load(:active_record) do
18
18
  ActiveRecord::Base.extend TimeRangeUniqueness::ModelAdditions
19
- ActiveRecord::Base.include TimeRangeUniqueness::ModelAdditions
20
19
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: time_range_uniqueness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - j-boers-13
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -16,119 +15,37 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '5.2'
18
+ version: '7.1'
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
- version: '8.0'
21
+ version: '9.0'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- version: '5.2'
28
+ version: '7.1'
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
- version: '8.0'
31
+ version: '9.0'
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: pg
35
34
  requirement: !ruby/object:Gem::Requirement
36
35
  requirements:
37
36
  - - ">="
38
37
  - !ruby/object:Gem::Version
39
- version: '0.18'
38
+ version: '1.5'
40
39
  type: :runtime
41
40
  prerelease: false
42
41
  version_requirements: !ruby/object:Gem::Requirement
43
42
  requirements:
44
43
  - - ">="
45
44
  - !ruby/object:Gem::Version
46
- version: '0.18'
47
- - !ruby/object:Gem::Dependency
48
- name: dotenv
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- - !ruby/object:Gem::Dependency
62
- name: rake
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- - !ruby/object:Gem::Dependency
76
- name: rspec
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: '0'
89
- - !ruby/object:Gem::Dependency
90
- name: rubocop
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: '0'
103
- - !ruby/object:Gem::Dependency
104
- name: rubocop-performance
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
- - !ruby/object:Gem::Dependency
118
- name: rubocop-rspec
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: '0'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: '0'
131
- description:
45
+ version: '1.5'
46
+ description: This gem helps you easily set up time range uniqueness constraints in
47
+ PostgreSQL using ActiveRecord migrations and validations. It ensures that time ranges
48
+ do not overlap within a table, supporting optional scoping of uniqueness.
132
49
  email:
133
50
  - jeroen.boers1@gmail.com
134
51
  executables: []
@@ -136,13 +53,10 @@ extensions: []
136
53
  extra_rdoc_files:
137
54
  - README.md
138
55
  files:
139
- - ".idea/.gitignore"
140
- - ".idea/misc.xml"
141
- - ".idea/modules.xml"
142
- - ".idea/time_range_uniqueness.iml"
143
- - ".idea/vcs.xml"
144
56
  - ".rspec"
145
57
  - ".rubocop.yml"
58
+ - ".ruby-version"
59
+ - ".tool-versions"
146
60
  - CHANGELOG.md
147
61
  - LICENSE.txt
148
62
  - README.md
@@ -151,13 +65,14 @@ files:
151
65
  - lib/time_range_uniqueness/migration_additions.rb
152
66
  - lib/time_range_uniqueness/model_additions.rb
153
67
  - lib/time_range_uniqueness/version.rb
154
- - time_range_uniqueness.gemspec
155
- homepage:
68
+ homepage: https://github.com/j-boers-13/time_range_uniqueness
156
69
  licenses:
157
70
  - MIT
158
71
  metadata:
72
+ source_code_uri: https://github.com/j-boers-13/time_range_uniqueness
73
+ homepage_uri: https://github.com/j-boers-13/time_range_uniqueness
74
+ changelog_uri: https://github.com/j-boers-13/time_range_uniqueness/CHANGELOG.md
159
75
  rubygems_mfa_required: 'true'
160
- post_install_message:
161
76
  rdoc_options: []
162
77
  require_paths:
163
78
  - lib
@@ -165,15 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
80
  requirements:
166
81
  - - ">="
167
82
  - !ruby/object:Gem::Version
168
- version: 2.6.0
83
+ version: 3.2.0
169
84
  required_rubygems_version: !ruby/object:Gem::Requirement
170
85
  requirements:
171
86
  - - ">="
172
87
  - !ruby/object:Gem::Version
173
88
  version: '0'
174
89
  requirements: []
175
- rubygems_version: 3.5.3
176
- signing_key:
90
+ rubygems_version: 3.6.7
177
91
  specification_version: 4
178
92
  summary: Easily set up time range uniqueness in Ruby On Rails.
179
93
  test_files: []
data/.idea/.gitignore DELETED
@@ -1,8 +0,0 @@
1
- # Default ignored files
2
- /shelf/
3
- /workspace.xml
4
- # Editor-based HTTP Client requests
5
- /httpRequests/
6
- # Datasource local storage ignored files
7
- /dataSources/
8
- /dataSources.local.xml
data/.idea/misc.xml DELETED
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectRootManager" version="2" project-jdk-name="Remote-asdf: ruby-3.3.0-p0" project-jdk-type="RUBY_SDK" />
4
- </project>
data/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/time_range_uniqueness.iml" filepath="$PROJECT_DIR$/.idea/time_range_uniqueness.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,62 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="RUBY_MODULE" version="4">
3
- <component name="ModuleRunConfigurationManager">
4
- <shared />
5
- </component>
6
- <component name="NewModuleRootManager">
7
- <content url="file://$MODULE_DIR$">
8
- <sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
9
- <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
10
- <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
11
- </content>
12
- <orderEntry type="inheritedJdk" />
13
- <orderEntry type="sourceFolder" forTests="false" />
14
- <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
15
- <orderEntry type="library" scope="PROVIDED" name="bundler (v2.5.5, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
16
- <orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.3, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
17
- <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
18
- <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
19
- <orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.6, Remote-asdf: ruby-3.3.0-p0) [gem]" level="application" />
20
- </component>
21
- <component name="RakeTasksCache-v2">
22
- <option name="myRootTask">
23
- <RakeTaskImpl id="rake">
24
- <subtasks>
25
- <RakeTaskImpl description="Build time_range_uniqueness-0.1.0.gem into the pkg directory" fullCommand="build" id="build" />
26
- <RakeTaskImpl id="build">
27
- <subtasks>
28
- <RakeTaskImpl description="Generate SHA512 checksum if time_range_uniqueness-0.1.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
29
- </subtasks>
30
- </RakeTaskImpl>
31
- <RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
32
- <RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
33
- <RakeTaskImpl description="Build and install time_range_uniqueness-0.1.0.gem into system gems" fullCommand="install" id="install" />
34
- <RakeTaskImpl id="install">
35
- <subtasks>
36
- <RakeTaskImpl description="Build and install time_range_uniqueness-0.1.0.gem into system gems without network access" fullCommand="install:local" id="local" />
37
- </subtasks>
38
- </RakeTaskImpl>
39
- <RakeTaskImpl description="Create tag v0.1.0 and build and push time_range_uniqueness-0.1.0.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
40
- <RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
41
- <RakeTaskImpl id="rubocop">
42
- <subtasks>
43
- <RakeTaskImpl description="Autocorrect RuboCop offenses (only when it's safe)" fullCommand="rubocop:autocorrect" id="autocorrect" />
44
- <RakeTaskImpl description="Autocorrect RuboCop offenses (safe and unsafe)" fullCommand="rubocop:autocorrect_all" id="autocorrect_all" />
45
- <RakeTaskImpl description="" fullCommand="rubocop:auto_correct" id="auto_correct" />
46
- </subtasks>
47
- </RakeTaskImpl>
48
- <RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
49
- <RakeTaskImpl description="" fullCommand="default" id="default" />
50
- <RakeTaskImpl description="" fullCommand="release" id="release" />
51
- <RakeTaskImpl id="release">
52
- <subtasks>
53
- <RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
54
- <RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
55
- <RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
56
- </subtasks>
57
- </RakeTaskImpl>
58
- </subtasks>
59
- </RakeTaskImpl>
60
- </option>
61
- </component>
62
- </module>
data/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/time_range_uniqueness/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'time_range_uniqueness'
7
- spec.version = TimeRangeUniqueness::VERSION
8
- spec.authors = ['j-boers-13']
9
- spec.email = ['jeroen.boers1@gmail.com']
10
-
11
- spec.summary = 'Easily set up time range uniqueness in Ruby On Rails.'
12
- # spec.description = "TODO: Write a longer description or delete this line."
13
- # spec.homepage = "TODO: Put your gem's website or public repo URL here."
14
- spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 2.6.0'
16
-
17
- # Specify which files should be added to the gem when it is released.
18
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(__dir__) do
20
- `git ls-files -z`.split("\x0").reject do |f|
21
- (File.expand_path(f) == __FILE__) ||
22
- f.start_with?(*%w[bin/ test/ spec/ features/ .git Gemfile])
23
- end
24
- end
25
- spec.extra_rdoc_files = ['README.md']
26
- spec.bindir = 'exe'
27
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
- spec.require_paths = ['lib']
29
-
30
- spec.add_dependency 'activerecord', '>= 5.2', '< 8.0'
31
- spec.add_dependency 'pg', '>= 0.18'
32
-
33
- spec.add_development_dependency 'dotenv'
34
- spec.add_development_dependency 'rake'
35
- spec.add_development_dependency 'rspec'
36
- spec.add_development_dependency 'rubocop'
37
- spec.add_development_dependency 'rubocop-performance'
38
- spec.add_development_dependency 'rubocop-rspec'
39
- spec.metadata['rubygems_mfa_required'] = 'true'
40
- end