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

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

Overview

Provides helpers to deal with tagtrees.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tagtree = [], depth: -1,, path: [], 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.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/eco/api/organization/tag_tree.rb', line 21

def initialize(tagtree = [], depth: -1, path: [], enviro: nil)
  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

  @depth = depth
  @tag = @source.is_a?(Array) ? nil : @source.dig('tag')&.upcase

  @path  = path || []
  @path.push(@tag) unless !@tag

  nodes = @source.is_a?(Array) ? @source : @source.dig('nodes') || []
  @nodes = nodes.map {|cnode| TagTree.new(cnode, depth: @depth + 1, path: @path.dup, enviro: @enviro)}
  @children_count = @nodes.count

  init_hashes
end

Instance Attribute Details

#children_countObject (readonly)

Returns the value of attribute children_count.



7
8
9
# File 'lib/eco/api/organization/tag_tree.rb', line 7

def children_count
  @children_count
end

#depthObject (readonly)

Returns the value of attribute depth.



8
9
10
# File 'lib/eco/api/organization/tag_tree.rb', line 8

def depth
  @depth
end

#enviroObject (readonly)

Returns the value of attribute enviro.



9
10
11
# File 'lib/eco/api/organization/tag_tree.rb', line 9

def enviro
  @enviro
end

#nodesObject (readonly)

Returns the value of attribute nodes.



7
8
9
# File 'lib/eco/api/organization/tag_tree.rb', line 7

def nodes
  @nodes
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>)


155
156
157
# File 'lib/eco/api/organization/tag_tree.rb', line 155

def path
  @path
end

#tagObject

Returns the value of attribute tag.



7
8
9
# File 'lib/eco/api/organization/tag_tree.rb', line 7

def tag
  @tag
end

Instance Method Details

#as_jsonArray[Hash]

Returns where Hash is a node {"tag" => TAG, "nodes": Array[Hash]}.

Returns:

  • (Array[Hash])

    where Hash is a node {"tag" => TAG, "nodes": Array[Hash]}



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/eco/api/organization/tag_tree.rb', line 66

def as_json
  nodes_json = nodes.map {|node| node.as_json}
  if top?
    nodes_json
  else
    {
      "tag"   => tag,
      "nodes" => nodes_json
    }
  end
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.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/eco/api/organization/tag_tree.rb', line 203

def default_tag(*values)
  values = filter_tags(values)
  nodes = []; depth = -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 > depth
      nodes = [cnode]
      depth = cnode.depth
    elsif cnode.depth == depth
      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 && depth > 0
  end
  default_tag = nodes.first&.tag  if !default_tag && ( (depth > 0) || flat?)
  default_tag
end

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

Returns with the differences.

Returns:

  • (Array)

    with the differences



56
57
58
59
# File 'lib/eco/api/organization/tag_tree.rb', line 56

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



51
52
53
# File 'lib/eco/api/organization/tag_tree.rb', line 51

def dup
  self.class.new(as_json)
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.



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

def empty?
  @has_tags.empty?
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>)


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

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.



94
95
96
# File 'lib/eco/api/organization/tag_tree.rb', line 94

def flat?
  self.total_depth <= 0
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.



137
138
139
140
# File 'lib/eco/api/organization/tag_tree.rb', line 137

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

#subtag?(key) ⇒ Boolean

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

Parameters:

  • key (String)

    tag to verify.

Returns:

  • (Boolean)


123
124
125
# File 'lib/eco/api/organization/tag_tree.rb', line 123

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>)


116
117
118
# File 'lib/eco/api/organization/tag_tree.rb', line 116

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)


130
131
132
# File 'lib/eco/api/organization/tag_tree.rb', line 130

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>)


104
105
106
107
108
109
110
111
112
# File 'lib/eco/api/organization/tag_tree.rb', line 104

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)


61
62
63
# File 'lib/eco/api/organization/tag_tree.rb', line 61

def top?
  depth == -1
end

#total_depthInteger

Returns the highest depth of all the children.

Returns:

  • (Integer)

    the highest depth of all the children.



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

def total_depth
  @total_depth ||= if children_count > 0
      deepest_node = nodes.max_by {|node| node.total_depth}
      deepest_node.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.



183
184
185
186
187
188
189
190
191
192
193
# File 'lib/eco/api/organization/tag_tree.rb', line 183

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