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 = [], name: nil, id: nil, 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.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/eco/api/organization/tag_tree.rb', line 24

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

  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
    @row_nodes = @source
  else
    @id    = @source.values_at('tag', 'id').compact.first&.upcase
    @name  = @source['name']
    @row_nodes = @source['nodes'] || []
  end

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

  @nodes = @row_nodes.map do |cnode|
    TagTree.new(cnode, depth: depth + 1, path: @path.dup, enviro: @enviro)
  end

  init_hashes
end

Instance Attribute Details

#children_countInteger (readonly)

Returns:

  • (Integer)


150
151
152
# File 'lib/eco/api/organization/tag_tree.rb', line 150

def children_count
  @children_count
end

#depthObject (readonly)

Returns the value of attribute depth.



11
12
13
# File 'lib/eco/api/organization/tag_tree.rb', line 11

def depth
  @depth
end

#enviroObject (readonly)

Returns the value of attribute enviro.



12
13
14
# File 'lib/eco/api/organization/tag_tree.rb', line 12

def enviro
  @enviro
end

#idObject Also known as: tag

Returns the value of attribute id.



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

def id
  @id
end

#nameObject

Returns the value of attribute name.



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

def name
  @name
end

#nodesObject (readonly)

Returns the value of attribute nodes.



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

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


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

def path
  @path
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]}



73
74
75
76
77
78
79
80
81
82
83
# File 'lib/eco/api/organization/tag_tree.rb', line 73

def as_json
  nodes_json = nodes.map {|node| node.as_json}
  if top?
    nodes_json
  else
    {
      "id"    => tag,
      "nodes" => nodes_json
    }
  end
end

#countInteger

Returns the number of locations.

Returns:

  • (Integer)

    the number of locations



91
92
93
# File 'lib/eco/api/organization/tag_tree.rb', line 91

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.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/eco/api/organization/tag_tree.rb', line 235

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



63
64
65
66
# File 'lib/eco/api/organization/tag_tree.rb', line 63

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



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

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.



86
87
88
# File 'lib/eco/api/organization/tag_tree.rb', line 86

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


177
178
179
180
# File 'lib/eco/api/organization/tag_tree.rb', line 177

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.



108
109
110
# File 'lib/eco/api/organization/tag_tree.rb', line 108

def flat?
  self.total_depth <= 0
end

#has_children?Boolean

Returns it has subnodes.

Returns:

  • (Boolean)

    it has subnodes



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

def has_children?
  children_count > 0
end

#leafsArray<String>

Returns all the tags with no children

Returns:

  • (Array<String>)


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

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.



169
170
171
172
# File 'lib/eco/api/organization/tag_tree.rb', line 169

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)


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

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


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

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)


162
163
164
# File 'lib/eco/api/organization/tag_tree.rb', line 162

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


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

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)


68
69
70
# File 'lib/eco/api/organization/tag_tree.rb', line 68

def top?
  depth == -1
end

#total_depthInteger

Returns the highest depth of all the children.

Returns:

  • (Integer)

    the highest depth of all the children.



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

def total_depth
  @total_depth ||= if has_children?
      deepest_node = nodes.max_by do |node|
        node.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.



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/eco/api/organization/tag_tree.rb', line 215

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