Class: Eco::API::Organization::TagTree

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/eco/api/organization/tag_tree.rb

Overview

Note:

that currenlty the parsing assumes top level to be array. This does not allow to capture the name and id of the locations structure itself into the json storing model.

Provides helpers to deal with tagtrees.

Constant Summary collapse

HEADER =
[
  'id', 'name', 'weight', 'parent_id',
  'archived', 'archived_token'
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tagtree = [], name: nil, id: nil, depth: -1,, path: [], parent: nil, _weight: nil, enviro: nil) ⇒ TagTree

Returns a new instance of TagTree.

Examples:

Node format:

{"tag": "NODE NAME", "nodes": subtree}

Tree/subtree format:

[[Node], ...]

Input format example:

tree = [{"tag" => "AUSTRALIA", "nodes" => [
    {"tag" => "SYDNEY", "nodes" => []}
]}]
tree = TagTree.new(tree.to_json)

Parameters:

  • tagtree (String) (defaults to: [])

    representation of the tagtree in json.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/eco/api/organization/tag_tree.rb', line 38

def initialize(tagtree = [], name: nil, id: nil, depth: -1, path: [], parent: nil, _weight: nil, enviro: nil)
  @depth  = depth
  @parent = parent

  case tagtree
  when String
    @source = JSON.parse(tagtree)
  else
    @source = tagtree
  end
  fatal("You are trying to initialize a TagTree with a null tagtree") if !@source
  fatal("Expecting Environment object. Given: #{enviro}") if enviro && !enviro.is_a?(API::Common::Session::Environment)
  @enviro = enviro

  if @source.is_a?(Array)
    @id        = id
    @name      = name
    @raw_nodes = @source
  else
    @id        = @source.values_at('tag', 'id').compact.first&.upcase
    @name      = @source['name']
    @archived  = @source['archived'] || false
    @archived_token   = @source['archived_token']
    @source['weight'] = @weight = @source['weight'] || _weight
    @raw_nodes = @source['nodes'] || []
  end

  @path  = path || []
  @path.push(@id) unless top?

  @nodes = @raw_nodes.map.with_index do |cnode, idx|
    TagTree.new(cnode, depth: depth + 1, path: @path.dup, parent: self, _weight: idx, enviro: @enviro)
  end

  init_hashes
end

Instance Attribute Details

#archivedObject

Returns the value of attribute archived.



17
18
19
# File 'lib/eco/api/organization/tag_tree.rb', line 17

def archived
  @archived
end

#archived_tokenObject

Returns the value of attribute archived_token.



17
18
19
# File 'lib/eco/api/organization/tag_tree.rb', line 17

def archived_token
  @archived_token
end

#children_countInteger (readonly)

Returns:

  • (Integer)


252
253
254
# File 'lib/eco/api/organization/tag_tree.rb', line 252

def children_count
  @children_count
end

#depthObject (readonly)

Returns the value of attribute depth.



21
22
23
# File 'lib/eco/api/organization/tag_tree.rb', line 21

def depth
  @depth
end

#enviroObject (readonly)

Returns the value of attribute enviro.



22
23
24
# File 'lib/eco/api/organization/tag_tree.rb', line 22

def enviro
  @enviro
end

#idObject Also known as: tag

Returns the value of attribute id.



14
15
16
# File 'lib/eco/api/organization/tag_tree.rb', line 14

def id
  @id
end

#nameObject

Returns the value of attribute name.



16
17
18
# File 'lib/eco/api/organization/tag_tree.rb', line 16

def name
  @name
end

#nodesObject (readonly)

Returns the value of attribute nodes.



20
21
22
# File 'lib/eco/api/organization/tag_tree.rb', line 20

def nodes
  @nodes
end

#parentObject (readonly)

Returns the value of attribute parent.



19
20
21
# File 'lib/eco/api/organization/tag_tree.rb', line 19

def parent
  @parent
end

#path(key = nil) ⇒ Array<String> (readonly)

Note:

the path is not relative to the subtree, but absolute to the entire tree.

Finds the path from a node key to its root node in the tree. If key is not specified, returns the path from current node to root.

Parameters:

  • key (String) (defaults to: nil)

    tag to find the path to.

Returns:

  • (Array<String>)


289
290
291
# File 'lib/eco/api/organization/tag_tree.rb', line 289

def path
  @path
end

#sourceObject (readonly)

Returns the value of attribute source.



24
25
26
# File 'lib/eco/api/organization/tag_tree.rb', line 24

def source
  @source
end

#weightObject

Returns the value of attribute weight.



16
17
18
# File 'lib/eco/api/organization/tag_tree.rb', line 16

def weight
  @weight
end

Instance Method Details

#all_nodes(&block) ⇒ Array<TagTree>

Note:

order is that of the parent to child relationships

All actual nodes of this tree

Returns:



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/eco/api/organization/tag_tree.rb', line 117

def all_nodes(&block)
  [].tap do |out_nodes|
    unless top?
      out_nodes.push(self)
      yield(self) if block_given?
    end
    nodes.each do |nd|
      out_nodes.concat(nd.all_nodes(&block))
    end
  end
end

#ancestorsArray<TagTree>

Note:

it does not include the current node

All the acenstor nodes of the current node

Returns:

  • (Array<TagTree>)

    ancestors sorted from top to bottom.



132
133
134
135
136
137
138
139
# File 'lib/eco/api/organization/tag_tree.rb', line 132

def ancestors
  [].tap do |ans|
    unless parent.top?
      ans << parent
      ans.concat(parent.ancestors)
    end
  end
end

#archived?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/eco/api/organization/tag_tree.rb', line 75

def archived?
  @archived
end

#as_json(include_children: true) {|node_json, node| ... } ⇒ Array[Hash]

Returns a tree of Hashes form nested via nodes (or just a list of hash nodes)

Yields:

  • (node_json, node)

    block for custom output json model

Returns:

  • (Array[Hash])

    where Hash is a node (i.e. {"tag" => TAG, "nodes": Array[Hash]})



166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/eco/api/organization/tag_tree.rb', line 166

def as_json(include_children: true, &block)
  return [] if top? && !include_children
  children_json = nodes.map {|nd| nd.as_json(include_children: true, &block)} if include_children

  if top?
    children_json
  else
    values    = [id, name, weight, parent_id, archived, archived_token]
    node_json = self.class::HEADER.zip(values).to_h
    node_json["nodes"] = children_json if include_children
    node_json = yield(node_json, self) if block_given?
    node_json
  end
end

#as_nodes_jsonArray[Hash]

Returns a plain list form of hash nodes.

Returns:

  • (Array[Hash])

    where Hash is a plain node



183
184
185
# File 'lib/eco/api/organization/tag_tree.rb', line 183

def as_nodes_json
  all_nodes.map {|nd| nd.as_json(include_children: false)}
end

#countInteger

Returns the number of locations.

Returns:

  • (Integer)

    the number of locations



193
194
195
# File 'lib/eco/api/organization/tag_tree.rb', line 193

def count
  @hash_tags.keys.count
end

#default_tag(*values) ⇒ String

Helper to decide which among the tags will be the default.

  • take the deepest tag (the one that is further down in the tree)
  • if there are different options (several nodes at the same depth):
    • take the common node between them (i.e. you have Hamilton and Auckland -> take New Zealand)
    • if there's no common node between them, take the first, unless they are at top level of the tree
    • to the above, take the first also on top level, but only if there's 1 level for the entire tree

Parameters:

  • values (Array<String>)

    list of tags.

Returns:

  • (String)

    default tag.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/eco/api/organization/tag_tree.rb', line 337

def default_tag(*values)
  values = filter_tags(values)
  nodes = []; ddepth = -1
  values.each do |tag|
    raise("Couldn't find the node of #{tag} in the tag-tree definition") unless cnode = node(tag)

    if cnode.depth > ddepth
      nodes = [cnode]
      ddepth = cnode.depth
    elsif cnode.depth == ddepth
      nodes.push(cnode)
    end
  end

  default_tag = nil
  if nodes.length > 1
    common      = nodes.reduce(self.tags.reverse) {|com, cnode| com & cnode.path.reverse}
    default_tag = common.first if common.length > 0 && ddepth > 0
  end
  default_tag ||= nodes.first&.tag  if (ddepth > 0) || flat?
  default_tag
end

#diff(tagtree, differences: {}, level: 0, **options) ⇒ Array

Returns with the differences.

Returns:

  • (Array)

    with the differences



152
153
154
155
# File 'lib/eco/api/organization/tag_tree.rb', line 152

def diff(tagtree, differences: {}, level: 0, **options)
  require 'hashdiff'
  Hashdiff.diff(self.as_json, tagtree.as_json, **options.slice(:array_path, :similarity, :use_lcs))
end

#dupEco::API::Organization::TagTree



80
81
82
# File 'lib/eco/api/organization/tag_tree.rb', line 80

def dup
  self.class.new(as_json)
end

#each {|node| ... } ⇒ Enumerable<Eco::API::Organization::TagTree>

Iterate through all the nodes of this tree

Yields:

  • (node)

    do some stuff with one of the nodes of the tree

Yield Parameters:

Returns:



88
89
90
91
# File 'lib/eco/api/organization/tag_tree.rb', line 88

def each(&block)
  return to_enum(:each) unless block
  all_nodes.each(&block)
end

#empty?Boolean

Returns true if there are tags in the node, false otherwise.

Returns:

  • (Boolean)

    true if there are tags in the node, false otherwise.



188
189
190
# File 'lib/eco/api/organization/tag_tree.rb', line 188

def empty?
  count <= 1
end

#filter_tags(list) ⇒ Array<String>

Filters tags out that do not belong to the tree

Parameters:

  • list (Array<String>)

    source tags.

Returns:

  • (Array<String>)


279
280
281
282
# File 'lib/eco/api/organization/tag_tree.rb', line 279

def filter_tags(list)
  return [] unless list && list.is_a?(Array)
  list.select {|str| tag?(str)}
end

#flat?Integer

Returns if there's only top level.

Returns:

  • (Integer)

    if there's only top level.



210
211
212
# File 'lib/eco/api/organization/tag_tree.rb', line 210

def flat?
  self.total_depth <= 0
end

#has_children?Boolean

Returns it has subnodes.

Returns:

  • (Boolean)

    it has subnodes



257
258
259
# File 'lib/eco/api/organization/tag_tree.rb', line 257

def has_children?
  children_count > 0
end

#leafsArray<String>

Returns all the tags with no children

Returns:

  • (Array<String>)


245
246
247
248
249
# File 'lib/eco/api/organization/tag_tree.rb', line 245

def leafs
  tags.select do |tag|
    !node(tag).has_children?
  end
end

#node(key) ⇒ TagTree?

Finds a subtree node.

Parameters:

  • key (String)

    parent node of subtree.

Returns:

  • (TagTree, nil)

    if the tag key is a node, returns that node.



271
272
273
274
# File 'lib/eco/api/organization/tag_tree.rb', line 271

def node(key)
  return nil unless tag?(key)
  @hash_tags[key.upcase]
end

#parent_idString

Returns the id of the parent (unless we are on a top level node).

Returns:

  • (String)

    the id of the parent (unless we are on a top level node)



142
143
144
# File 'lib/eco/api/organization/tag_tree.rb', line 142

def parent_id
  parent.id unless parent.top?
end

#parent_nameString

Returns the name of the parent (unless we are on a top level node).

Returns:

  • (String)

    the name of the parent (unless we are on a top level node)



147
148
149
# File 'lib/eco/api/organization/tag_tree.rb', line 147

def parent_name
  parent.name unless parent.top?
end

#reject(&block) ⇒ Array<TagTree>

Note:

rejected nodes will not include their children nodes

Returns plain list of nodes.

Returns:

  • (Array<TagTree>)

    plain list of nodes



110
111
112
# File 'lib/eco/api/organization/tag_tree.rb', line 110

def reject(&block)
  select(when_is: false, &block)
end

#select(when_is: true, &block) ⇒ Array<TagTree>

Note:

rejected nodes will not include their children nodes

Returns plain list of nodes.

Returns:

  • (Array<TagTree>)

    plain list of nodes

Raises:

  • (ArgumentError)


95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/eco/api/organization/tag_tree.rb', line 95

def select(when_is: true, &block)
  raise ArgumentError, "Missing block" unless block_given?
  [].tap do |out_nodes|
    selected = false
    selected = (yield(self) == when_is) unless top?
    out_nodes.push(self)                if selected
    next                                unless selected || top?
    nodes.each do |nd|
      out_nodes.concat(nd.select(when_is: when_is, &block))
    end
  end
end

#subtag?(key) ⇒ Boolean

Verifies if a tag exists in the subtree(s).

Parameters:

  • key (String)

    tag to verify.

Returns:

  • (Boolean)


239
240
241
# File 'lib/eco/api/organization/tag_tree.rb', line 239

def subtag?(key)
  subtags.include?(key&.upcase)
end

#subtagsArray<String>

Gets all but the upper level tags of the current node tree.

Returns:

  • (Array<String>)


232
233
234
# File 'lib/eco/api/organization/tag_tree.rb', line 232

def subtags
  tags - tags(depth: depth)
end

#tag?(key) ⇒ Boolean

Verifies if a tag exists in the tree.

Parameters:

  • key (String)

    tag to verify.

Returns:

  • (Boolean)


264
265
266
# File 'lib/eco/api/organization/tag_tree.rb', line 264

def tag?(key)
  @hash_tags.key?(key&.upcase)
end

#tags(depth: nil) ⇒ Array<String>

Note:
  • this will include the upper level tag(s) as well
  • to get all but the upper level tag(s) use subtags method instead

Gets all the tags of the current node tree.

Parameters:

  • depth (Integer) (defaults to: nil)

    if empty, returns the list of tag nodes of that level. Otherwise the list of tag nodes of the entire subtree.

Returns:

  • (Array<String>)


220
221
222
223
224
225
226
227
228
# File 'lib/eco/api/organization/tag_tree.rb', line 220

def tags(depth: nil)
  if !depth || depth < 0
    @hash_tags.keys
  else
    @hash_tags.select do |t, n|
      n.depth == depth
    end.keys
  end
end

#top?Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/eco/api/organization/tag_tree.rb', line 157

def top?
  depth == -1
end

#total_depthInteger

Returns the highest depth of all the children.

Returns:

  • (Integer)

    the highest depth of all the children.



198
199
200
201
202
203
204
205
206
207
# File 'lib/eco/api/organization/tag_tree.rb', line 198

def total_depth
  @total_depth ||= if has_children?
      deepest_node = nodes.max_by do |nd|
        nd.total_depth
      end
      deepest_node.total_depth
    else
      depth
    end
end

#user_tags(initial: [], final: [], preserve_custom: true, add_custom: false) ⇒ Array<String>

Helper to assign tags to a person account.

  • It preserves the :initial order, in case the :final tags are the same

Examples:

Usage example:

tree = [{"tag" => "Australia", "nodes" => [
     {"tag" => "SYDNEY", "nodes" => []},
     {"tag" => "MELBOURNE", "nodes" => []}
]}]

tree = TagTree.new(tree.to_json)
original = ["SYDNEY", "RISK"]
final    = ["MELBOURNE", "EVENT"]

tree.user_tags(initial: original, final: final) # out: ["MELBOURNE", "RISK"]
tree.user_tags(initial: original, final: final, preserve_custom: false) # out: ["MELBOURNE"]
tree.user_tags(initial: original, final: final, add_custom: true) # out: ["MELBOURNE", "RISK", "EVENT"]
tree.user_tags(initial: original, final: final, preserve_custom: false, add_custom: true) # out: ["MELBOURNE", "EVENT"]

Parameters:

  • initial (Array<String>) (defaults to: [])

    original tags a person has in their account.

  • final (Array<String>) (defaults to: [])

    target tags the person should have in their account afterwards.

  • preserve_custom (Boolean) (defaults to: true)

    indicates if original tags that are not in the tree should be added/preserved.

  • add_custom (Boolean) (defaults to: false)

    indicates if target tags that are not in the tree should be really added.

Returns:

  • (Array<String>)

    with the treated final tags.



317
318
319
320
321
322
323
324
325
326
327
# File 'lib/eco/api/organization/tag_tree.rb', line 317

def user_tags(initial: [], final: [], preserve_custom: true, add_custom: false)
  initial = [initial].flatten.compact
  final   = [final].flatten.compact
  raise "Expected Array for initial: and final:" unless initial.is_a?(Array) && final.is_a?(Array)
  final    = filter_tags(final) unless add_custom
  custom   = initial - filter_tags(initial)
  final    = final + custom     if preserve_custom
  new_tags = final - initial
  # keep same order as they where

  (initial & final) + new_tags
end