Class: Torque::PostgreSQL::Attributes::LTree

Inherits:
Array
  • Object
show all
Defined in:
lib/torque/postgresql/attributes/ltree.rb

Overview

A label path, as stored by the ltree data type. It is an Array of labels, so it flows through Ruby like any other list, which also means that an ltree[] column simply becomes an Array of these

Constant Summary collapse

LABEL =
/\A[[:alnum:]_-]+\z/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(labels = nil, normalize: true) ⇒ LTree

Returns a new instance of LTree.



68
69
70
71
# File 'lib/torque/postgresql/attributes/ltree.rb', line 68

def initialize(labels = nil, normalize: true)
  super()
  concat(normalize ? normalized(labels) : Array.wrap(labels))
end

Class Method Details

.[](*labels) ⇒ Object



15
16
17
# File 'lib/torque/postgresql/attributes/ltree.rb', line 15

def [](*labels)
  new(labels)
end

.compatible(value) ⇒ Object

The path that the object describes for itself



33
34
35
36
# File 'lib/torque/postgresql/attributes/ltree.rb', line 33

def compatible(value)
  method = PostgreSQL.config.ltree.compatible_method
  value.public_send(method) if compatible?(value)
end

.compatible?(value) ⇒ Boolean

Whether the object knows how to describe itself as a path, which is what allows any class to be used where a path is expected

Returns:

  • (Boolean)


27
28
29
30
# File 'lib/torque/postgresql/attributes/ltree.rb', line 27

def compatible?(value)
  method = PostgreSQL.config.ltree.compatible_method
  method.present? && value.respond_to?(method)
end

.load(value) ⇒ Object

Values coming from the database are valid by construction, so they skip both the normalization and the validation



21
22
23
# File 'lib/torque/postgresql/attributes/ltree.rb', line 21

def load(value)
  new(value.to_s.split('.'), normalize: false)
end

.resolve_record(value) ⇒ Object

A record stands for its primary key, which is what makes a path built out of other records work the same way as one built out of labels

Raises:

  • (ArgumentError)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/torque/postgresql/attributes/ltree.rb', line 41

def resolve_record(value)
  return value unless value.is_a?(::ActiveRecord::Base)

  id = value.id
  raise ArgumentError, <<~MSG.squish if id.nil?
    Unable to use #{value.class.name} as a label because its
    #{value.class.primary_key} is still empty.
  MSG

  raise ArgumentError, <<~MSG.squish if id.is_a?(::Array)
    Unable to use #{value.class.name} as a label because it has a
    composite primary key, which cannot be a single label.
  MSG

  id
end

.sanitize(value) ⇒ Object

Apply the configured replacements, so callers can feed a source that does not satisfy PostgreSQL's rules for a label on its own



60
61
62
63
64
65
# File 'lib/torque/postgresql/attributes/ltree.rb', line 60

def sanitize(value)
  replacements = PostgreSQL.config.ltree.sanitize
  return value if replacements.blank?

  value.gsub(Regexp.union(replacements.keys), replacements)
end

Instance Method Details

#/(other) ⇒ Object Also known as: +



90
91
92
# File 'lib/torque/postgresql/attributes/ltree.rb', line 90

def /(other)
  self.class.new(to_a + self.class.new(other), normalize: false)
end

#ancestor_of?(other) ⇒ Boolean Also known as: covers?

Same as the @> operator, which includes the path itself

Returns:

  • (Boolean)


97
98
99
100
# File 'lib/torque/postgresql/attributes/ltree.rb', line 97

def ancestor_of?(other)
  other = self.class.new(other)
  size <= other.size && other.first(size) == to_a
end

#descendant_of?(other) ⇒ Boolean Also known as: covered_by?

Same as the <@ operator, which includes the path itself

Returns:

  • (Boolean)


104
105
106
# File 'lib/torque/postgresql/attributes/ltree.rb', line 104

def descendant_of?(other)
  self.class.new(other).ancestor_of?(self)
end

#index_of(subpath, offset = 0) ⇒ Object

The position where the given subpath starts, or -1 when it is not present. Named apart from index so that Array's own contract, of returning nil when the element is missing, stays intact



121
122
123
124
125
126
127
128
129
# File 'lib/torque/postgresql/attributes/ltree.rb', line 121

def index_of(subpath, offset = 0)
  subpath = self.class.new(subpath)
  offset += size if offset.negative?
  return -1 if subpath.empty? || offset.negative?

  range = offset..(size - subpath.size)
  position = range.find { |i| self[i, subpath.size] == subpath.to_a }
  position || -1
end

#lca(*others) ⇒ Object

The longest common ancestor, which never includes the last label of any of the paths, exactly like PostgreSQL's own lca



111
112
113
114
115
116
# File 'lib/torque/postgresql/attributes/ltree.rb', line 111

def lca(*others)
  paths = [self, *others].map { |path| self.class.new(path)[0..-2].to_a }
  result = paths.shift || []
  paths.each { |path| result = common_prefix(result, path) }
  self.class.new(result, normalize: false)
end

#parentObject

A path with a single label has no parent, and neither does an empty one



86
87
88
# File 'lib/torque/postgresql/attributes/ltree.rb', line 86

def parent
  self.class.new(self[0..-2], normalize: false) unless root?
end

#rootObject



81
82
83
# File 'lib/torque/postgresql/attributes/ltree.rb', line 81

def root
  self.class.new(first, normalize: false)
end

#root?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/torque/postgresql/attributes/ltree.rb', line 77

def root?
  size <= 1
end

#to_sObject



73
74
75
# File 'lib/torque/postgresql/attributes/ltree.rb', line 73

def to_s
  join('.')
end