Class: Eco::Data::Locations::NodeLevel

Inherits:
NodeLevelStruct show all
Extended by:
Builder
Includes:
NodeBase
Defined in:
lib/eco/data/locations/node_level.rb,
lib/eco/data/locations/node_level/serial.rb,
lib/eco/data/locations/node_level/builder.rb,
lib/eco/data/locations/node_level/cleaner.rb,
lib/eco/data/locations/node_level/parsing.rb

Overview

Class to treat input csv in a form of levels, where each column is one level, and children are placed in higher columns right below the parent.

Defined Under Namespace

Modules: Builder, Cleaner, Parsing, Serial

Constant Summary collapse

ALL_ATTRS =
NODE_LEVEL_ATTRS
ADDITIONAL_ATTRS =
i[row_num].freeze
TAGS_ATTRS =
(ALL_ATTRS - ADDITIONAL_ATTRS).freeze

Constants included from Eco::Data::Locations::NodeBase::TagValidations

Eco::Data::Locations::NodeBase::TagValidations::ALLOWED_CHARACTERS, Eco::Data::Locations::NodeBase::TagValidations::DOUBLE_BLANKS, Eco::Data::Locations::NodeBase::TagValidations::INVALID_TAG_REGEX, Eco::Data::Locations::NodeBase::TagValidations::VALID_TAG_CHARS, Eco::Data::Locations::NodeBase::TagValidations::VALID_TAG_REGEX

Instance Attribute Summary collapse

Attributes included from Serial

#serializer

Attributes included from Language::AuxiliarLogger

#logger

Attributes included from Parsing

#node_class

Attributes included from NodeBase

#parent, #tracked_level

Instance Method Summary collapse

Methods included from Serial

#nodes_to_csv_tree

Methods included from Convert

#csv_from, #empty_array, #empty_level_tracker_hash, #hash_tree_to_tree_csv, #log_pretty_inspect, #normalize_arrays, #report_repeated_node_ids

Methods included from Language::AuxiliarLogger

#log

Methods included from Cleaner

#done_ids, #fill_in_parents, #repeated_ids, #reset_trackers!, #tidy_nodes

Methods included from Parsing

#csv_matches_format?, #nodes_from_csv

Methods included from NodeBase

#attr, #attr?, #set_attr, #set_attrs, #slice, #to_h, #values_at

Methods included from Eco::Data::Locations::NodeBase::Builder

#node_class

Methods included from Eco::Data::Locations::NodeBase::Treeify

#serialize_node, #treeify

Methods included from Eco::Data::Locations::NodeBase::CsvConvert

#csv_list, #csv_tree, #hash_list, #hash_tree, #org_tree, #tree_class

Methods included from Eco::Data::Locations::NodeBase::Parsing

#csv_nodes_from, #hash_tree_from_csv, #nodes_from_csv

Methods included from Eco::Data::Locations::NodeBase::Serial

#nodes_to_csv_list, #nodes_to_csv_tree, #serializer

Methods included from Eco::Data::Locations::NodeBase::TagValidations

#clean_id, #has_double_blanks?, #identify_invalid_characters, #invalid_warned, #invalid_warned!, #invalid_warned?, #remove_double_blanks, #replace_not_allowed

Instance Attribute Details

#original_headersObject



71
72
73
# File 'lib/eco/data/locations/node_level.rb', line 71

def original_headers
  @original_headers ||= []
end

#parent_idObject Also known as: parentId



42
43
44
# File 'lib/eco/data/locations/node_level.rb', line 42

def parent_id
  @parent_id&.upcase
end

Instance Method Details

#actual_levelObject



93
94
95
# File 'lib/eco/data/locations/node_level.rb', line 93

def actual_level
  tags_array.compact.length
end

#blanks_between?Boolean

Returns:

  • (Boolean)


254
255
256
# File 'lib/eco/data/locations/node_level.rb', line 254

def blanks_between?
  actual_level > empty_idx
end

#classification_namesObject



85
86
87
# File 'lib/eco/data/locations/node_level.rb', line 85

def classification_names
  classifications.dup
end

#classificationsObject



79
80
81
82
83
# File 'lib/eco/data/locations/node_level.rb', line 79

def classifications
  return [] unless (al = actual_level).positive?

  original_headers[al - 1]
end

#clean_parent_idObject



143
144
145
# File 'lib/eco/data/locations/node_level.rb', line 143

def clean_parent_id
  clean_tags_array.compact[-2]
end

#clean_tags_arrayObject



258
259
260
261
262
# File 'lib/eco/data/locations/node_level.rb', line 258

def clean_tags_array
  tags_array.map do |tg|
    clean_id(tg, notify: false)
  end
end

#clear_level(i) ⇒ Object

Clears the deepest levels, from level i onwards



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/eco/data/locations/node_level.rb', line 203

def clear_level(i)
  case i
  when Enumerable
    target = i.to_a
  when Integer
    return false unless i >= 1 && i <= tag_attrs_count

    target = Array(i..tag_attrs_count)
  else
    return false
  end

  return false if target.empty?

  target.each do |n|
    #puts "clearing 'l#{n}': #{attr("l#{n}")}"

    set_attr("l#{n}", nil)
  end
  true
end

#common_level_with(other) ⇒ Object

Requires that all upper levels (lower positions) are filled-in



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/eco/data/locations/node_level.rb', line 121

def common_level_with(other)
  return nil unless other

  otags_array = other.tags_array.compact
  stags_array = tags_array.compact

  raise "Missing lower levels for #{other.id}: #{other.tags_array.pretty_inspect}" unless other.highest_levels_set?
  raise "Missing lower levels for #{id}:      #{tags_array.pretty_inspect}"        unless highest_levels_set?

  otags_array.zip(stags_array).each_with_index do |(otag, stag), idx|
    next       if otag&.upcase&.strip == stag&.upcase&.strip
    return nil if idx&.zero?
    return idx # previous idx, which means prev_idx + 1 (so idx itself)

  end
  actual_level
end

#copyObject



31
32
33
34
35
36
# File 'lib/eco/data/locations/node_level.rb', line 31

def copy
  super.tap do |cp|
    cp.highest_levels_set!
    cp.original_headers = original_headers
  end
end

#decouple(num = 1) ⇒ Object

Note:

for each one in the gap, creates a copy and clears deepest levels thereof

We got a missing level that is compacted in one row Here we get the missing intermediate levels This is done from upper to lower level to ensure processing order It skips last one, as that is this object already



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/eco/data/locations/node_level.rb', line 166

def decouple(num = 1)
  with_info = filled_idxs
  # must be the last among filled_idxs, so let's use it to verify

  unless with_info.last == tag_idx
    # This can only happen when there are repeated nodes

    msg  = "Review this (row #{row_num}; '#{raw_tag}'): "
    msg << "tag_idx is #{tag_idx}, while last filled idx is #{with_info.last}"
    raise msg
  end

  len         = with_info.length
  target_idxs = with_info[len-(num+1)..-2]
  target_idxs.map do |idx|
    copy.tap do |dup|
      dup.clear_level(idx_to_level(idx + 1))
    end
  end
end

#empty_idxObject



156
157
158
159
# File 'lib/eco/data/locations/node_level.rb', line 156

def empty_idx
  tary = tags_array
  tary.index(nil) || (tary.length + 1)
end

#filled_idxsObject



248
249
250
251
252
# File 'lib/eco/data/locations/node_level.rb', line 248

def filled_idxs
  tags_array.each_with_index.with_object([]) do |(tg, ix), out|
    out << ix if tg
  end
end

#highest_levels_set!Object



192
193
194
# File 'lib/eco/data/locations/node_level.rb', line 192

def highest_levels_set!
  @highest_levels_set = true
end

#highest_levels_set?Boolean

Returns:

  • (Boolean)


185
186
187
188
189
190
# File 'lib/eco/data/locations/node_level.rb', line 185

def highest_levels_set?
  return true if raw_level == 1
  return true unless raw_prev_empty_level?

  !!@highest_levels_set
end

#idObject Also known as: node_id, nodeId



47
48
49
# File 'lib/eco/data/locations/node_level.rb', line 47

def id
  tag.upcase
end

#idx_to_level(x) ⇒ Object



240
241
242
# File 'lib/eco/data/locations/node_level.rb', line 240

def idx_to_level(x)
  x + 1
end

#levelObject



89
90
91
# File 'lib/eco/data/locations/node_level.rb', line 89

def level
  actual_level
end

#level_to_idx(x) ⇒ Object



244
245
246
# File 'lib/eco/data/locations/node_level.rb', line 244

def level_to_idx(x)
  x - 1
end

#nameObject



57
58
59
# File 'lib/eco/data/locations/node_level.rb', line 57

def name
  tag
end

#node_hash(stringify_keys: true) {|node, json| ... } ⇒ Object

Yields:

  • (node, json)

    optional custom serializer

Yield Parameters:

  • node (Node)

    self

  • json (Hash)

    the default serialization

Yield Returns:

  • (Hash)

    the serialized Node



24
25
26
27
28
29
# File 'lib/eco/data/locations/node_level.rb', line 24

def node_hash(stringify_keys: true)
  json = to_h(:id, :name, :parent_id, :classifications, :classification_names)
  json.transform_keys!(&:to_s)   if stringify_keys
  json.merge!(yield(self, json)) if block_given?
  json
end

#original_headers?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/eco/data/locations/node_level.rb', line 75

def original_headers?
  original_headers.any?
end

#previous_idxObject



151
152
153
154
# File 'lib/eco/data/locations/node_level.rb', line 151

def previous_idx
  idx = tag_idx - 1
  idx&.negative?? nil : idx
end

#raw_latest_consecutive_top_empty_levelObject



113
114
115
116
117
118
# File 'lib/eco/data/locations/node_level.rb', line 113

def raw_latest_consecutive_top_empty_level
  tags_array[0..raw_level-1].each_with_index do |value, idx|
    return idx if value
  end
  nil
end

#raw_levelObject



97
98
99
# File 'lib/eco/data/locations/node_level.rb', line 97

def raw_level
  tags_array.index(raw_tag) + 1
end

#raw_parent_idObject

Second last id in tags_array



139
140
141
# File 'lib/eco/data/locations/node_level.rb', line 139

def raw_parent_id
  tags_array.compact[-2]
end

#raw_prev_empty_levelObject



101
102
103
104
105
106
# File 'lib/eco/data/locations/node_level.rb', line 101

def raw_prev_empty_level
  tags_array[0..raw_level-1].each_with_index.reverse_each do |value, idx|
    return idx + 1 unless value
  end
  nil
end

#raw_prev_empty_level?Boolean

Returns:

  • (Boolean)


108
109
110
111
# File 'lib/eco/data/locations/node_level.rb', line 108

def raw_prev_empty_level?
  lev = raw_prev_empty_level
  lev&.positive?
end

#raw_tagObject



65
66
67
# File 'lib/eco/data/locations/node_level.rb', line 65

def raw_tag
  values_at(*TAGS_ATTRS.reverse).compact.first
end

#self_parented?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/eco/data/locations/node_level.rb', line 53

def self_parented?
  id == parent_id
end

#set_high_levels(node) ⇒ Object

Sets ancestors



197
198
199
200
# File 'lib/eco/data/locations/node_level.rb', line 197

def set_high_levels(node)
  update_lower_levels(node.tags_array)
  self
end

#tagObject



61
62
63
# File 'lib/eco/data/locations/node_level.rb', line 61

def tag
  clean_id(raw_tag, ref: "(Row: #{row_num}) ")
end

#tag_attrs_countObject



268
269
270
# File 'lib/eco/data/locations/node_level.rb', line 268

def tag_attrs_count
  TAGS_ATTRS.length
end

#tag_idxObject



147
148
149
# File 'lib/eco/data/locations/node_level.rb', line 147

def tag_idx
  tags_array.index(raw_tag)
end

#tags_arrayObject



264
265
266
# File 'lib/eco/data/locations/node_level.rb', line 264

def tags_array
  values_at(*TAGS_ATTRS)
end

#update_lower_levels(src_tags_array, to_level: raw_latest_consecutive_top_empty_level) ⇒ Object

Ensures parent is among the upper level tags It actually ensures all ancestors are there

Parameters:

  • override (Boolean)

    false will only override upmost top consecutive empty levels.



227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/eco/data/locations/node_level.rb', line 227

def update_lower_levels(src_tags_array, to_level: raw_latest_consecutive_top_empty_level)
  highest_levels_set!
  return self unless to_level

  target_lev  = Array(1..to_level)
  target_tags = src_tags_array[level_to_idx(1)..level_to_idx(to_level)]
  target_lev.zip(target_tags).each do |(n, tag)|
    attr_lev = "l#{n}"
    set_attr(attr_lev, tag) # unless attr?(attr_lev) && !override

  end
  self
end