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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +37 -0
- data/lib/arel/array_predications.rb +9 -1
- data/lib/arel/extensions/version.rb +1 -1
- data/lib/arel/extensions.rb +8 -0
- data/lib/arel/nodes/adjacent_to.rb +12 -0
- data/lib/arel/nodes/not_extend_left_of.rb +12 -0
- data/lib/arel/nodes/not_extend_right_of.rb +12 -0
- data/lib/arel/nodes/strictly_left_of.rb +13 -0
- data/lib/arel/nodes/strictly_right_of.rb +13 -0
- data/lib/arel/range_predications.rb +38 -0
- data/lib/arel/visitors/sunstone_extensions.rb +12 -4
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3d568adf807cce6bc64a82bdf1ec35047475de73c85d0e4db25a8f452064869
|
|
4
|
+
data.tar.gz: 4a3724dca9138cf0d1220346d010254247ae92c4ebcfd8dae629d0455a2e6b5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
data/lib/arel/extensions.rb
CHANGED
|
@@ -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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|