arel-extensions 9.0.1 → 9.1.0

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: 99efadd34c12e304c5204c9dc2717bad533bfb20c9c280a036a8abc3110ab34a
4
- data.tar.gz: 66c064d703b2531c7676e3a91bef9f0dd813355e24108c7966770ea1fba298ff
3
+ metadata.gz: f3d568adf807cce6bc64a82bdf1ec35047475de73c85d0e4db25a8f452064869
4
+ data.tar.gz: 4a3724dca9138cf0d1220346d010254247ae92c4ebcfd8dae629d0455a2e6b5b
5
5
  SHA512:
6
- metadata.gz: 043be028082fea8d31c8b427db8a703b7f4a2f1916879e77455f05847086964840b4cae0886e73930ace056dc9e86bcb7c375c4794a64f5ac5a85c19d5f9b61b
7
- data.tar.gz: 89d8e35f5503774cc499c93fa0cc9ea427dec2cecc27cd6ee01f5d52f9f66f9c278078c80c865d7fc4bac8362e9c860f7fe33b1a90b572c4823621cfe6be9ba3
6
+ metadata.gz: 4f2f03dea174a2e6b4d708dc0a6fe7bb6e43af18135a4a4802fbb844278139bb7690d2d2dff38d9436767f49442d2483c1142dfa89c2555d92f97efc82813292
7
+ data.tar.gz: 85e605b87fb83bc2d36edd0ccab58fb85d15bebf0bbb4cf74b05a249354800664d5ae656f09141b7c0a50bb49e5c8b2d0649b331d5ac6d67b78cc378a0296e3f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [9.1.0] - 2026-09-10
2
+
3
+ ### Added
4
+
5
+ - PostgreSQL's positional range operators on attributes: `strictly_left_of`
6
+ (`<<`), `strictly_right_of` (`>>`), `not_extend_right_of` (`&<`),
7
+ `not_extend_left_of` (`&>`) and `adjacent_to` (`-|-`).
8
+
9
+ ### Changed
10
+
11
+ - The Sunstone visitor handles `Arel::Nodes::Not` in place of the
12
+ `Arel::Nodes::NotOverlaps` it used to name, which never existed — so
13
+ `attribute.overlaps(x).not` now serializes where nothing could before.
14
+
15
+ ### Fixed
16
+
17
+ - `contained_by` now accepts a Ruby Range, so range columns work with all three
18
+ range predicates.
19
+
1
20
  ## [9.0.1] - 2026-08-30
2
21
 
3
22
  ### Security
data/README.md CHANGED
@@ -56,8 +56,45 @@ A `RANDOM()` ordering node is also provided.
56
56
  tags = Post.arel_table[:tags]
57
57
  tags.contained_by(other) # tags <@ other
58
58
  tags.excludes(other) # NOT (tags @> other)
59
+
60
+ # Any predicate negates with Arel's own #not:
61
+ tags.overlaps(other).not # NOT (tags && other)
62
+ ```
63
+
64
+ ### Range columns
65
+
66
+ ActiveRecord types `tsrange`/`tstzrange`/`daterange`/`int4range`/`int8range`/
67
+ `numrange` columns as `OID::Range`, so a plain Ruby Range serializes to a
68
+ PostgreSQL range literal — no special node needed:
69
+
70
+ ```ruby
71
+ period = Property.arel_table[:period]
72
+
73
+ period.contains(t1...t2) # "period" @> '[2026-01-01 00:00:00,2026-12-31 00:00:00)'
74
+ period.overlaps(t1...t2) # "period" && '[...)'
75
+ period.contained_by(t1...t2) # "period" <@ '[...)'
59
76
  ```
60
77
 
78
+ `..` gives an inclusive upper bound (`']'`), `...` an exclusive one (`')'`), and
79
+ a `nil` end is unbounded. PostgreSQL's exclusive *lower* bounds (`'('`) have no
80
+ Ruby Range equivalent; build the range with a `NamedFunction` if you need one.
81
+
82
+ Beyond containment and overlap, PostgreSQL's positional operators ask where two
83
+ ranges sit relative to one another:
84
+
85
+ ```ruby
86
+ period.strictly_left_of(other) # period << other
87
+ period.strictly_right_of(other) # period >> other
88
+ period.not_extend_right_of(other) # period &< other
89
+ period.not_extend_left_of(other) # period &> other
90
+ period.adjacent_to(other) # period -|- other
91
+ ```
92
+
93
+ `<<` and `>>` mean every element is lower (or higher) with no overlap. `&<` asks
94
+ whether the left range stops at or before the right one's upper bound, and `&>`
95
+ whether it starts at or after the right one's lower bound. `-|-` is true when the
96
+ two abut — touching, with no gap and no overlap.
97
+
61
98
  ### JSON / JSONB predicates
62
99
 
63
100
  ```ruby
@@ -3,8 +3,16 @@
3
3
  module Arel
4
4
  module ArrayPredications
5
5
 
6
- # Used by both JSON and ARRAY so it doesn't try to cast to array
6
+ # Used by both JSON and ARRAY so it doesn't try to cast to array — callers
7
+ # there pre-wrap the value themselves.
8
+ #
9
+ # A Ruby Range is the exception: it has no other reading, and leaving it
10
+ # unquoted made `contained_by` the one range predicate that could not take
11
+ # one (`contains` and `overlaps` come from Arel core, which quotes through
12
+ # the attribute). On a range column the attribute's type casts it to a
13
+ # PostgreSQL range literal.
7
14
  def contained_by(value)
15
+ value = Arel::Nodes.build_quoted(value, self) if value.is_a?(::Range)
8
16
  Arel::Nodes::ContainedBy.new(self, value)
9
17
  end
10
18
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Arel
4
4
  module Extensions
5
- VERSION = '9.0.1'
5
+ VERSION = '9.1.0'
6
6
  end
7
7
  end
@@ -9,10 +9,18 @@ require_relative "./nodes/intersects"
9
9
  require_relative "./nodes/within"
10
10
  require_relative "./nodes/excludes"
11
11
  require_relative "./nodes/contained_by"
12
+ require_relative "./nodes/strictly_left_of"
13
+ require_relative "./nodes/strictly_right_of"
14
+ require_relative "./nodes/not_extend_right_of"
15
+ require_relative "./nodes/not_extend_left_of"
16
+ require_relative "./nodes/adjacent_to"
12
17
 
13
18
  require File.expand_path('../array_predications', __FILE__)
14
19
  Arel::Attributes::Attribute.include(Arel::ArrayPredications)
15
20
 
21
+ require File.expand_path('../range_predications', __FILE__)
22
+ Arel::Attributes::Attribute.include(Arel::RangePredications)
23
+
16
24
  require File.expand_path('../nodes/random', __FILE__)
17
25
  require File.expand_path(File.join(__FILE__, '../../../ext/arel/nodes/ascending'))
18
26
  require File.expand_path(File.join(__FILE__, '../../../ext/arel/nodes/descending'))
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ # PostgreSQL `-|-`. Do the two ranges abut — touching, with no gap and no overlap?
6
+ class AdjacentTo < InfixOperation
7
+ def initialize(left, right)
8
+ super(:"-|-", left, right)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ # PostgreSQL `&>`. Does the left range start at or after the right one's lower bound?
6
+ class NotExtendLeftOf < InfixOperation
7
+ def initialize(left, right)
8
+ super(:"&>", left, right)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ # PostgreSQL `&<`. Does the left range stop at or before the right one's upper bound?
6
+ class NotExtendRightOf < InfixOperation
7
+ def initialize(left, right)
8
+ super(:"&<", left, right)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ # PostgreSQL `<<`. Is the left range strictly left of the right one — every element lower,
6
+ # with no overlap?
7
+ class StrictlyLeftOf < InfixOperation
8
+ def initialize(left, right)
9
+ super(:"<<", left, right)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ # PostgreSQL `>>`. Is the left range strictly right of the right one — every element higher,
6
+ # with no overlap?
7
+ class StrictlyRightOf < InfixOperation
8
+ def initialize(left, right)
9
+ super(:">>", left, right)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ # PostgreSQL's positional range operators. Each takes a range and answers a
5
+ # question about where the two sit relative to one another, which `contains`,
6
+ # `overlaps` and `contained_by` cannot express.
7
+ #
8
+ # Operands are quoted through the attribute the way Arel core's `overlaps`
9
+ # does, so a Ruby Range works and an already-built node passes through.
10
+ module RangePredications
11
+
12
+ # <<
13
+ def strictly_left_of(value)
14
+ Arel::Nodes::StrictlyLeftOf.new(self, Arel::Nodes.build_quoted(value, self))
15
+ end
16
+
17
+ # >>
18
+ def strictly_right_of(value)
19
+ Arel::Nodes::StrictlyRightOf.new(self, Arel::Nodes.build_quoted(value, self))
20
+ end
21
+
22
+ # &<
23
+ def not_extend_right_of(value)
24
+ Arel::Nodes::NotExtendRightOf.new(self, Arel::Nodes.build_quoted(value, self))
25
+ end
26
+
27
+ # &>
28
+ def not_extend_left_of(value)
29
+ Arel::Nodes::NotExtendLeftOf.new(self, Arel::Nodes.build_quoted(value, self))
30
+ end
31
+
32
+ # -|-
33
+ def adjacent_to(value)
34
+ Arel::Nodes::AdjacentTo.new(self, Arel::Nodes.build_quoted(value, self))
35
+ end
36
+
37
+ end
38
+ end
@@ -114,10 +114,18 @@ module Arel
114
114
  end
115
115
  end
116
116
 
117
- def visit_Arel_Nodes_NotOverlaps o, collector
118
- key = visit(o.left, collector)
119
- value = { not_overlaps: visit(o.right, collector) }
120
-
117
+ # A negated Overlaps — `attribute.overlaps(x).not` — is the only NOT this
118
+ # adapter knows how to serialize, so the negation is unwrapped here.
119
+ def visit_Arel_Nodes_Not o, collector
120
+ expr = o.expr
121
+
122
+ unless expr.is_a?(Arel::Nodes::Overlaps)
123
+ raise "Not Supported: NOT of #{expr.class}"
124
+ end
125
+
126
+ key = visit(expr.left, collector)
127
+ value = { not_overlaps: visit(expr.right, collector) }
128
+
121
129
  if key.is_a?(Hash)
122
130
  add_to_bottom_of_hash_or_array(key, value)
123
131
  key
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arel-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.1
4
+ version: 9.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
@@ -178,6 +178,7 @@ files:
178
178
  - lib/arel/extensions/version.rb
179
179
  - lib/arel/gis_predications.rb
180
180
  - lib/arel/json_predications.rb
181
+ - lib/arel/nodes/adjacent_to.rb
181
182
  - lib/arel/nodes/binary_value.rb
182
183
  - lib/arel/nodes/contained_by.rb
183
184
  - lib/arel/nodes/excludes.rb
@@ -187,14 +188,19 @@ files:
187
188
  - lib/arel/nodes/has_keys.rb
188
189
  - lib/arel/nodes/hex_encoded_binary_value.rb
189
190
  - lib/arel/nodes/intersects.rb
191
+ - lib/arel/nodes/not_extend_left_of.rb
192
+ - lib/arel/nodes/not_extend_right_of.rb
190
193
  - lib/arel/nodes/random.rb
191
194
  - lib/arel/nodes/relation.rb
195
+ - lib/arel/nodes/strictly_left_of.rb
196
+ - lib/arel/nodes/strictly_right_of.rb
192
197
  - lib/arel/nodes/ts_match.rb
193
198
  - lib/arel/nodes/ts_query.rb
194
199
  - lib/arel/nodes/ts_rank.rb
195
200
  - lib/arel/nodes/ts_rank_cd.rb
196
201
  - lib/arel/nodes/ts_vector.rb
197
202
  - lib/arel/nodes/within.rb
203
+ - lib/arel/range_predications.rb
198
204
  - lib/arel/ts_predications.rb
199
205
  - lib/arel/visitors/postgresql_extensions.rb
200
206
  - lib/arel/visitors/sunstone_extensions.rb