eco-helpers 3.0.25 → 3.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/eco-helpers.gemspec +1 -1
  4. data/lib/eco/api/common/loaders/base.rb +13 -1
  5. data/lib/eco/api/common/loaders/case_base.rb +1 -1
  6. data/lib/eco/api/common/loaders/error_handler.rb +1 -1
  7. data/lib/eco/api/common/loaders/parser.rb +2 -2
  8. data/lib/eco/api/common/loaders/policy.rb +1 -1
  9. data/lib/eco/api/common/loaders/use_case.rb +1 -1
  10. data/lib/eco/api/common/people/default_parsers/archived_parser.rb +19 -0
  11. data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +4 -4
  12. data/lib/eco/api/common/people/default_parsers/date_parser.rb +3 -3
  13. data/lib/eco/api/common/people/default_parsers/freemium_parser.rb +6 -6
  14. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +3 -3
  15. data/lib/eco/api/common/people/default_parsers/multi_parser.rb +4 -4
  16. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +9 -6
  17. data/lib/eco/api/common/people/default_parsers/send_invites_parser.rb +6 -5
  18. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +2 -2
  19. data/lib/eco/api/common/people/default_parsers.rb +1 -0
  20. data/lib/eco/api/common/people/entries.rb +16 -15
  21. data/lib/eco/api/common/people/person_entry.rb +53 -37
  22. data/lib/eco/api/common/people/person_parser.rb +8 -6
  23. data/lib/eco/api/common/session/logger/channels.rb +1 -1
  24. data/lib/eco/api/common/session/logger.rb +2 -2
  25. data/lib/eco/api/common/session/mailer/aws_provider.rb +3 -2
  26. data/lib/eco/api/common/session/mailer/provider_base.rb +6 -2
  27. data/lib/eco/api/common/session/mailer/sendgrid_provider.rb +9 -9
  28. data/lib/eco/api/common/session/mailer.rb +8 -3
  29. data/lib/eco/api/common/session/sftp.rb +2 -2
  30. data/lib/eco/api/common/version_patches/object.rb +2 -1
  31. data/lib/eco/api/custom/mailer.rb +1 -1
  32. data/lib/eco/api/error/handlers.rb +3 -3
  33. data/lib/eco/api/error.rb +17 -17
  34. data/lib/eco/api/microcases/people/apply_changes/set_account/account_excluded.rb +3 -11
  35. data/lib/eco/api/microcases/people/apply_changes/set_core/core_excluded.rb +4 -1
  36. data/lib/eco/api/microcases/people/manage/search.rb +3 -1
  37. data/lib/eco/api/organization/people.rb +1 -0
  38. data/lib/eco/api/policies.rb +2 -2
  39. data/lib/eco/api/session/batch/job.rb +2 -2
  40. data/lib/eco/api/session/batch/launcher.rb +4 -5
  41. data/lib/eco/api/session/batch/searcher.rb +4 -4
  42. data/lib/eco/api/session/config/api.rb +2 -2
  43. data/lib/eco/api/session/config/apis/enviro_spaces.rb +3 -3
  44. data/lib/eco/api/session/config/apis/space_helpers.rb +4 -4
  45. data/lib/eco/api/session/config/apis.rb +1 -1
  46. data/lib/eco/api/session/config/post_launch.rb +7 -4
  47. data/lib/eco/api/session/config/sftp.rb +1 -1
  48. data/lib/eco/api/session/config/tagtree.rb +1 -1
  49. data/lib/eco/api/session/config/workflow.rb +4 -4
  50. data/lib/eco/api/session/config.rb +25 -24
  51. data/lib/eco/api/session.rb +4 -4
  52. data/lib/eco/api/usecases/base_io/validations.rb +4 -3
  53. data/lib/eco/api/usecases/cli/option.rb +5 -2
  54. data/lib/eco/api/usecases/default/people/amend/clear_abilities_case.rb +2 -2
  55. data/lib/eco/api/usecases/default/people/amend/restore_db_case.rb +16 -9
  56. data/lib/eco/api/usecases/default/people/treat/analyse_people_case.rb +20 -20
  57. data/lib/eco/api/usecases/default.rb +5 -5
  58. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +8 -8
  59. data/lib/eco/api/usecases/default_cases/upsert_case.rb +4 -4
  60. data/lib/eco/api/usecases/graphql/helpers/base.rb +1 -1
  61. data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +5 -1
  62. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +2 -1
  63. data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +3 -3
  64. data/lib/eco/api/usecases/graphql/helpers/location/command.rb +3 -0
  65. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +2 -2
  66. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +9 -3
  67. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +5 -4
  68. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +5 -2
  69. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +3 -3
  70. data/lib/eco/api/usecases/graphql/utils/sftp.rb +1 -1
  71. data/lib/eco/api/usecases/lib/{file_pattern.rb → files/file_pattern.rb} +5 -1
  72. data/lib/eco/api/usecases/lib/{sftp.rb → files/sftp.rb} +27 -16
  73. data/lib/eco/api/usecases/lib/files.rb +7 -0
  74. data/lib/eco/api/usecases/lib.rb +1 -2
  75. data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +5 -5
  76. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +14 -11
  77. data/lib/eco/api/usecases/ooze_samples/helpers_migration/copying.rb +14 -23
  78. data/lib/eco/api/usecases/ooze_samples/helpers_migration/typed_fields_pairing.rb +49 -27
  79. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +9 -9
  80. data/lib/eco/api/usecases/ooze_samples/ooze_run_base_case.rb +1 -1
  81. data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +25 -17
  82. data/lib/eco/api/usecases/ooze_samples/register_migration_case.rb +41 -24
  83. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +16 -15
  84. data/lib/eco/api/usecases/samples/drivers/cli/sftp_cli.rb +15 -15
  85. data/lib/eco/api/usecases/samples/drivers/cli/url_pull_cli.rb +5 -5
  86. data/lib/eco/api/usecases/samples/drivers/sftp_sample.rb +2 -2
  87. data/lib/eco/api/usecases/use_case.rb +6 -6
  88. data/lib/eco/api/usecases/use_case_chain/chaining.rb +6 -6
  89. data/lib/eco/api/usecases/use_case_chain.rb +4 -4
  90. data/lib/eco/api/usecases/use_case_io.rb +2 -1
  91. data/lib/eco/api/usecases.rb +9 -9
  92. data/lib/eco/cli/config/options_set.rb +4 -4
  93. data/lib/eco/cli/config/use_cases.rb +3 -3
  94. data/lib/eco/cli/scripting/argument.rb +1 -1
  95. data/lib/eco/cli_default/input.rb +9 -9
  96. data/lib/eco/cli_default/options.rb +125 -100
  97. data/lib/eco/cli_default/people.rb +3 -3
  98. data/lib/eco/cli_default/usecases.rb +83 -83
  99. data/lib/eco/cli_default/workflow.rb +7 -7
  100. data/lib/eco/data/files/helpers.rb +3 -3
  101. data/lib/eco/data/fuzzy_match/result.rb +69 -26
  102. data/lib/eco/data/fuzzy_match/results.rb +10 -10
  103. data/lib/eco/data/fuzzy_match/score.rb +13 -8
  104. data/lib/eco/data/fuzzy_match.rb +65 -48
  105. data/lib/eco/data/locations/node_base/treeify.rb +13 -11
  106. data/lib/eco/data/mapper.rb +4 -4
  107. data/lib/eco/language/auxiliar_logger.rb +4 -4
  108. data/lib/eco/language/delegation/const_delegator.rb +64 -0
  109. data/lib/eco/language/delegation/const_lookup_hooks.rb +81 -0
  110. data/lib/eco/language/delegation/delegated_class.rb +84 -0
  111. data/lib/eco/language/delegation.rb +8 -0
  112. data/lib/eco/language/klass/when_inherited.rb +1 -0
  113. data/lib/eco/language/methods/delegate_missing.rb +1 -0
  114. data/lib/eco/language/models/class_helpers.rb +25 -23
  115. data/lib/eco/language/models/collection.rb +12 -2
  116. data/lib/eco/language.rb +1 -0
  117. data/lib/eco/version.rb +1 -1
  118. metadata +12 -6
@@ -23,7 +23,7 @@ module Eco
23
23
  include CharsPositionScore
24
24
  include NGramsScore
25
25
 
26
- def jaro_winkler(str1, str2, **options)
26
+ def jaro_winkler(str_1, str_2, **options)
27
27
  return 0 if !str1 || !str2
28
28
  options = {
29
29
  ignore_case: true,
@@ -31,26 +31,26 @@ module Eco
31
31
  }.merge(options)
32
32
 
33
33
  require 'jaro_winkler'
34
- JaroWinkler.distance(str1, str2, **options)
34
+ JaroWinkler.distance(str_1, str_2, **options)
35
35
  end
36
36
 
37
37
  end
38
38
 
39
39
  module InstanceMethods
40
- FUZZY_MATCH_OPTIONS = [
41
- :identities, :groupings, :stop_words, :read,
42
- :must_match_grouping, :must_match_at_least_one_word,
43
- :gather_last_result, :threshold
44
- ]
40
+ FUZZY_MATCH_OPTIONS = %i[
41
+ identities groupings stop_words read
42
+ must_match_grouping must_match_at_least_one_word
43
+ gather_last_result threshold
44
+ ].freeze
45
45
 
46
- JARO_OPTIONS = [:ignore_case, :weight]
47
- NGRAMS_OPTIONS = [:range]
48
- POSITION_OPTIONS = [:max_distance]
49
- RESULTS_OPTIONS = [:order, :threshold]
46
+ JARO_OPTIONS = %i[ignore_case weight].freeze
47
+ NGRAMS_OPTIONS = %i[range].freeze
48
+ POSITION_OPTIONS = %i[max_distance].freeze
49
+ RESULTS_OPTIONS = %i[order threshold].freeze
50
50
 
51
51
  include StopWords
52
52
 
53
- attr_accessor :fuzzy_options
53
+ attr_writer :fuzzy_options
54
54
 
55
55
  def fuzzy_options
56
56
  @fuzzy_options ||= {}
@@ -58,60 +58,71 @@ module Eco
58
58
 
59
59
  def fuzzy_match(haystack_data = nil, **options)
60
60
  if instance_variable_defined?(:@fuzzy_match) && !haystack_data
61
- return @fuzzy_match if fuzzy_match_options == fuzzy_match_options(options)
61
+ return @fuzzy_match if fuzzy_match_options == fuzzy_match_options(options) # rubocop:disable Style/SoleNestedConditional
62
62
  end
63
+
63
64
  @fuzzy_options = options
64
65
 
65
66
  # make it run with a native C extension (for better performance: ~130 % increase of performance)
66
67
  require 'fuzzy_match'
67
68
  require 'amatch'
69
+
68
70
  ::FuzzyMatch.engine = :amatch
69
71
  @fuzzy_match = ::FuzzyMatch.new(haystack(haystack_data), fuzzy_match_options)
70
72
  end
71
73
 
72
- # TODO: integration for options[:unique_words] => to ensure repeated words do not bring down the score are cut by threshold
74
+ # @todo integration for options[:unique_words] => to ensure repeated words do not bring down
75
+ # the score are cut by threshold
73
76
  # @note
74
77
  # - When the `haystack` elements are **non** `String` objects, it excludes the needle itself from the results
75
78
  # @param needle [String, Object] object is allowed when `fuzzy_options` includes `read:` key.
76
79
  # @param needle_str [String, nil] the actual value of needle_str to be used.
77
80
  # @param haystack [Enumerable] the items to find `needle` among.
78
81
  # @return [Eco::Data::FuzzyMatch::Results]
79
- def find_all_with_score(needle, needle_str: nil, haystack: nil, **options)
82
+ def find_all_with_score(needle, needle_str: nil, haystack: nil, **options) # rubocop:disable Metrics/AbcSize
80
83
  base_match = fuzzy_match(haystack, **options)
81
84
  match_results = base_match.find_all_with_score(needle_str || needle)
82
85
  needle_str ||= item_string(needle)
83
86
  results = match_results.each_with_object([]) do |fuzzy_results, results|
84
87
  item, dice, lev = fuzzy_results
85
- unless item == needle
86
- item_str = item_string(item)
87
-
88
- if item_str.to_s.strip.empty? || needle_str.to_s.strip.empty?
89
- dice = lev = jaro_res = ngram_res = ngram_res = wngram_res = pos_res = 0
90
- end
88
+ next if item == needle
91
89
 
92
- jaro_res ||= jaro(needle_str, item_str)
93
- ngram_res ||= ngram(needle_str, item_str)
94
- wngram_res ||= words_ngram(needle_str, item_str)
95
- pos_res ||= position(needle_str, item_str)
90
+ item_str = item_string(item)
96
91
 
97
- results << Result.new(item, item_str, needle_str, dice, lev, jaro_res, ngram_res, wngram_res, pos_res)
92
+ if item_str.to_s.strip.empty? || needle_str.to_s.strip.empty?
93
+ dice = lev = jaro_res = ngram_res = ngram_res = wngram_res = pos_res = 0
98
94
  end
95
+
96
+ jaro_res ||= jaro(needle_str, item_str)
97
+ ngram_res ||= ngram(needle_str, item_str)
98
+ wngram_res ||= words_ngram(needle_str, item_str)
99
+ pos_res ||= position(needle_str, item_str)
100
+
101
+ results << Result.new(item, item_str, needle_str, dice, lev, jaro_res, ngram_res, wngram_res, pos_res)
99
102
  end
103
+
100
104
  Results.new(needle, needle_str, results).tap do |res|
101
105
  res.order = fuzzy_options[:order] if fuzzy_options[:order]
102
106
  res.threshold = fuzzy_options[:threshold] if fuzzy_options[:threshold]
103
107
  end.relevant_results
104
108
  end
105
109
 
106
- def recalculate_results(results, needle_str: nil, **options)
107
- raise "You should provide a block |needle_str, item_str, needle, item|" unless block_given?
110
+ def recalculate_results(results, needle_str: nil, **options) # rubocop:disable Metrics/AbcSize
111
+ msg = "You should provide a block |needle_str, item_str, needle, item|"
112
+ raise msg unless block_given?
113
+
108
114
  new_results = results.each_with_object([]) do |result, new_results|
109
- nstr, istr = yield(needle_str || results.value, result.value, results.needle, result.match)
115
+ nstr, istr = yield(
116
+ needle_str || results.value,
117
+ result.value,
118
+ results.needle,
119
+ result.match
120
+ )
110
121
 
111
122
  if istr.to_s.strip.empty?
112
123
  dice = lev = jaro_res = ngram_res = ngram_res = wngram_res = pos_res = 1
113
124
  elsif nstr.to_s.strip.empty?
114
- unless istr = needle_str
125
+ unless (istr = needle_str)
115
126
  dice = lev = jaro_res = ngram_res = ngram_res = wngram_res = pos_res = 0
116
127
  end
117
128
  end
@@ -119,7 +130,7 @@ module Eco
119
130
  require 'fuzzy_match'
120
131
  require 'amatch'
121
132
  res = ::FuzzyMatch.score_class.new(nstr, istr) unless dice && lev
122
-
133
+
123
134
  dice ||= res&.dices_coefficient_similar || 0
124
135
  lev ||= res&.levenshtein_similar || 0
125
136
  jaro_res ||= jaro(nstr, istr)
@@ -129,6 +140,7 @@ module Eco
129
140
 
130
141
  new_results << Result.new(*result.values_at(:match, :value, :needle_str), dice, lev, jaro_res, ngram_res, wngram_res, pos_res)
131
142
  end
143
+
132
144
  Results.new(results.needle, results.value, new_results).tap do |res|
133
145
  res.order = options[:order] if options[:order]
134
146
  res.threshold = options[:threshold] if options[:threshold]
@@ -137,24 +149,24 @@ module Eco
137
149
 
138
150
  private
139
151
 
140
- def jaro(str1, str2)
152
+ def jaro(str_1, str_2)
141
153
  options = fuzzy_options.slice(*JARO_OPTIONS)
142
- self.class.jaro_winkler(str1, str2, **options)
154
+ self.class.jaro_winkler(str_1, str_2, **options)
143
155
  end
144
156
 
145
- def ngram(str1, str2)
157
+ def ngram(str_1, str_2)
146
158
  options = { range: 3..5 }.merge(fuzzy_options.slice(*NGRAMS_OPTIONS))
147
- self.class.ngrams_score(str1, str2, **options).ratio
159
+ self.class.ngrams_score(str_1, str_2, **options).ratio
148
160
  end
149
161
 
150
- def words_ngram(str1, str2)
162
+ def words_ngram(str_1, str_2)
151
163
  options = { range: 3..7 }.merge(fuzzy_options.slice(*NGRAMS_OPTIONS))
152
- self.class.words_ngrams_score(str1, str2, **options).ratio
164
+ self.class.words_ngrams_score(str_1, str_2, **options).ratio
153
165
  end
154
166
 
155
- def position(str1, str2)
167
+ def position(str_1, str_2)
156
168
  options = fuzzy_options.slice(*POSITION_OPTIONS)
157
- self.class.chars_position_score(str1, str2, **options).ratio
169
+ self.class.chars_position_score(str_1, str_2, **options).ratio
158
170
  end
159
171
 
160
172
  # @note
@@ -162,12 +174,18 @@ module Eco
162
174
  # @param data [Enumerable, nil]
163
175
  # @return [Array<Object>] the non-repeated values of `data`
164
176
  def haystack(data = nil)
165
- data = self if self.is_a?(Enumerable) && !data
166
- raise "'data' should be an Enumerable. Given: #{data.class}" unless data.is_a?(Enumerable)
167
- data = self.is_a?(Hash) ? self.values.flatten : to_a.flatten
177
+ data = self if is_a?(Enumerable) && !data
178
+
179
+ msg = "'data' should be an Enumerable. Given: #{data.class}"
180
+ raise ArgumentError, msg unless data.is_a?(Enumerable)
181
+
182
+ data = is_a?(Hash) ? values.flatten : to_a.flatten
183
+
168
184
  data.uniq.compact.tap do |items|
169
- if !fuzzy_read_method && found = items.find {|item| !item.is_a?(String)}
170
- raise "To use non String objects as 'haystack' you should provide `read:` or `options[:read]`. Given element: #{found.class}"
185
+ if !fuzzy_read_method && (found = items.find {|item| !item.is_a?(String)})
186
+ msg = "To use non String objects as 'haystack' you should provide `read:` or `options[:read]`. "
187
+ msg << "Given element: #{found.class}"
188
+ raise ArgumentError, msg
171
189
  end
172
190
  end
173
191
  end
@@ -175,12 +193,13 @@ module Eco
175
193
  def item_string(item, attr = fuzzy_read_method)
176
194
  return item if !item || item.is_a?(String) || !attr
177
195
  return attr.call(item) if attr.is_a?(Proc)
196
+
178
197
  attr = attr.to_sym
179
- return item.send(attr) if item.respond_to?(attr)
198
+ item.send(attr) if item.respond_to?(attr, true)
180
199
  end
181
200
 
182
201
  def fuzzy_match_options(options = nil)
183
- options = fuzzy_options unless options
202
+ options ||= fuzzy_options
184
203
  options.slice(*FUZZY_MATCH_OPTIONS).merge({
185
204
  stop_words: PREPOSITIONS + PRONOUNS + ARTICLES
186
205
  })
@@ -189,13 +208,11 @@ module Eco
189
208
  def fuzzy_read_method
190
209
  fuzzy_match_options[:read]
191
210
  end
192
-
193
211
  end
194
212
 
195
213
  class << self
196
214
  include FuzzyMatch::ClassMethods
197
215
  end
198
-
199
216
  end
200
217
  end
201
218
  end
@@ -16,7 +16,7 @@ module Eco::Data::Locations::NodeBase
16
16
  raise ArgumentError, msg unless node.is_a?(Eco::Data::Locations::NodeBase)
17
17
 
18
18
  node.node_hash.tap do |json|
19
- json.merge!({"parent_id" => parent_id}) unless parent_id == :unused
19
+ json.merge!({'parent_id' => parent_id}) unless parent_id == :unused
20
20
  json.merge!(yield(node, json)) if block_given?
21
21
  end
22
22
  end
@@ -90,7 +90,7 @@ module Eco::Data::Locations::NodeBase
90
90
 
91
91
  node_hash = serialize_node(child, parent_id: node_id, &block)
92
92
  results << node_hash
93
- node_hash["nodes"] = get_children(
93
+ node_hash['nodes'] = get_children(
94
94
  child.id, parents,
95
95
  parent: child, done_ids: done_ids,
96
96
  level: level + 1, skipped: skipped,
@@ -105,7 +105,7 @@ module Eco::Data::Locations::NodeBase
105
105
  end
106
106
 
107
107
  def parent_msg(parent)
108
- parent ? "child of '#{parent.id}'" : "top level"
108
+ parent ? "child of '#{parent.id}'" : 'top level'
109
109
  end
110
110
 
111
111
  def level_msg(level)
@@ -113,7 +113,7 @@ module Eco::Data::Locations::NodeBase
113
113
  end
114
114
 
115
115
  def indent(level)
116
- " " * level
116
+ ' ' * level
117
117
  end
118
118
 
119
119
  # Method to ensure the results are consistent
@@ -122,7 +122,7 @@ module Eco::Data::Locations::NodeBase
122
122
  # because otherwise would be part of `done_ids` anyway.
123
123
  # @param unlinked_trees [Array<Hash>] by excluding those done and skipped,
124
124
  # it will treeify the unlinked nodes (the exclusion applies to `parants_hash`)
125
- def check_results( # rubocop:disable Metrics/AbcSize
125
+ def check_results( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
126
126
  _tree, nodes, parents,
127
127
  done_ids: {}, skipped: [], unlinked_trees: [],
128
128
  warns: [], &block
@@ -138,9 +138,9 @@ module Eco::Data::Locations::NodeBase
138
138
 
139
139
  # The reason of missing nodes in the output tree is unknown!
140
140
  if skipped.empty? && unlinked_parent_ids.empty?
141
- msg = []
142
- msg << "BUG in this library (open issue with maintainers)."
143
- msg << "There were no skipped nodes nor missin referred parents, and yet:"
141
+ msg = []
142
+ msg << 'BUG in this library (open issue with maintainers).'
143
+ msg << 'There were no skipped nodes nor missin referred parents, and yet:'
144
144
  msg << " * the tree nodes count: #{done_ids.count} ..."
145
145
  msg << " * doesn't match the original nodes count: #{nodes.count}"
146
146
  raise msg.join("\n")
@@ -181,7 +181,7 @@ module Eco::Data::Locations::NodeBase
181
181
  str_skipped = (residual_skipped.count < 15 ? " => #{residual_skipped.map(&:id).join(', ')}" : '')
182
182
 
183
183
  msg = []
184
- msg << "After treeifying via the unlinked_parents:"
184
+ msg << 'After treeifying via the unlinked_parents:'
185
185
  msg << " * total_nodes: #{nodes.count}"
186
186
  msg << " * tracked_nodes: #{tracked_nodes.count}"
187
187
  msg << " * untracked_nodes: #{untracked_nodes.count}"
@@ -230,14 +230,16 @@ module Eco::Data::Locations::NodeBase
230
230
  node_hash = serialize_node(child, parent_id: node_id, &block) unless src_plain
231
231
  descendants = get_tree_nodes_raw(child.id, parents, src_plain: src_plain, &block).tap do |desc|
232
232
  next unless (nil_count = desc.count(nil)).positive?
233
+
233
234
  log(:debug) {
234
235
  "get_tree_nodes_raw gave #{nil_count} nil values for nodes of #{child.id}"
235
236
  }
236
237
  end
238
+
237
239
  next results.concat(descendants) if src_plain
238
240
 
239
241
  results << node_hash.merge({
240
- "nodes" => descendants.compact
242
+ 'nodes' => descendants.compact
241
243
  })
242
244
  end
243
245
  end
@@ -256,7 +258,7 @@ module Eco::Data::Locations::NodeBase
256
258
 
257
259
  # With given a skipped `node` (repeated `id`), it gives different warnings,
258
260
  # provided that the context in which the double-up `id` happened is identified.
259
- def report_skipped_node( # rubocop:disable Metrics/AbcSize
261
+ def report_skipped_node( # rubocop:disable Metrics/AbcSize, Metrics/ParameterLists, Metrics/MethodLength
260
262
  node, parent,
261
263
  done_ids, level,
262
264
  level_ids, parents,
@@ -2,7 +2,7 @@ module Eco
2
2
  module Data
3
3
  class Mapper
4
4
  # it expects [[v1a, v1b], [v2a, v2b] ...]
5
- def initialize (array_of_arrays = [], internal: :last, insensitive: false)
5
+ def initialize(array_of_arrays = [], internal: :last, insensitive: false)
6
6
  @internal_order = internal
7
7
  @source = array_of_arrays
8
8
  @insensitive = insensitive
@@ -23,7 +23,7 @@ module Eco
23
23
  end
24
24
  end
25
25
 
26
- @by_internal = @source.reverse.map(&:reverse).to_h.tap do |h_data|
26
+ @by_internal = @source.reverse.to_h(&:reverse).tap do |h_data|
27
27
  next unless insensitive?
28
28
 
29
29
  h_data.dup.each do |key, value|
@@ -57,7 +57,7 @@ module Eco
57
57
  src_dup.map(&:reverse)
58
58
  end
59
59
 
60
- def +(array_of_arrays)
60
+ def +(array_of_arrays) # rubocop:disable Naming/BinaryOperatorParameterName
61
61
  self.class.new(array_of_arrays + to_a, internal: @internal_order)
62
62
  end
63
63
 
@@ -70,7 +70,7 @@ module Eco
70
70
  end
71
71
 
72
72
  def internal?(value)
73
- return true unless @source
73
+ return true unless @source
74
74
 
75
75
  value = value.downcase if insensitive?
76
76
  @by_internal.key?(value)
@@ -9,8 +9,8 @@ module Eco
9
9
  def logger
10
10
  if instance_variable_defined?(:@session) && !@session.nil?
11
11
  @session.logger
12
- elsif respond_to?(:session)
13
- session.logger
12
+ elsif respond_to?(:session, true)
13
+ send(:session).logger
14
14
  elsif Object.const_defined?(:ASSETS)
15
15
  ASSETS.session.logger
16
16
  elsif defined?(super)
@@ -31,8 +31,8 @@ module Eco
31
31
  levels = levels.compact.uniq.map(&:to_sym)
32
32
  levels.unshift(:info) if levels.include?(:general) && levels.length == 1
33
33
 
34
- levels.each do |level|
35
- next unless logger.respond_to?(:level)
34
+ levels.uniq.each do |level|
35
+ next unless logger.respond_to?(:level, true)
36
36
 
37
37
  logger.send(level, &block)
38
38
  end
@@ -0,0 +1,64 @@
1
+ module Eco::Language::Delegation
2
+ # Module that is to be used with ruby `Delegator` or related helpers,
3
+ # such as `SimpleDelegator` or `Forwardable`.
4
+ # @note it includes `DelegatedClass`
5
+ # @note it looks up the constant via `delegated_class`. This is because
6
+ # on `Delegator` approaches, the intance object (`self`) points to the
7
+ # delegated object, which doesn't allow to refer to `self.class` to
8
+ # implement a delegation of constants that other way.
9
+ # For example, `self.class::CONSTANT` will not point to the delegator
10
+ # class constant, but to the constant of the delegated object's class.
11
+ module ConstDelegator
12
+ class << self
13
+ private
14
+
15
+ def included(base)
16
+ super
17
+
18
+ base.send :include, Eco::Language::DelegatedClass
19
+ base.extend(ClassMethods)
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def const_defined?(str, inherit = true, delegated: true)
25
+ return super(str, inherit) if super(str, inherit) || !delegated
26
+ return true if delegated_class&.const_defined?(str, inherit)
27
+
28
+ false
29
+ end
30
+
31
+ def const_get(str)
32
+ return super if const_defined?(str, delegated: false)
33
+ return delegated_class.const_get(str) if delegated_class&.const_defined?(str)
34
+
35
+ super
36
+ end
37
+
38
+ def const_missing(str)
39
+ return super unless const_defined?(str)
40
+
41
+ const_get(str)
42
+ end
43
+
44
+ def constants(inherited = true, delegated: true)
45
+ super(inherited).dup.tap do |consts|
46
+ next unless delegated && delegated_class
47
+
48
+ delegated_class.constants(inherited).each do |const|
49
+ consts.unshift(const)
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def remove_const(str)
57
+ return super unless const_defined?(str)
58
+ return super if const_defined?(str, delegated: false)
59
+
60
+ raise NameError, "#{str} not defined (is delegated to #{delegated_class})"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,81 @@
1
+ module Eco::Language::Delegation
2
+ # This approach only makes sense to be used in `Delegator` and related helpers.
3
+ # @note it includes `ConstDelegator` (which includes `DelegatedClass`)
4
+ # @note for each not inherited constant that isn't part of the delegated object's
5
+ # class, it will create a undercore/snake named instance method, to ease
6
+ # constant lookup througout chained/wrapped delegators.
7
+ module ConstLookupHooks
8
+ class << self
9
+ private
10
+
11
+ def included(base)
12
+ super
13
+
14
+ base.send :include, Eco::Language::Delegation::ConstDelegator
15
+
16
+ base.extend(ClassMethods)
17
+ base.send :install_constant_lookup_hooks
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ private
23
+
24
+ def inherited(subclass)
25
+ super
26
+
27
+ subclass.send :install_constant_lookup_hooks
28
+ end
29
+
30
+ def const_added(sym)
31
+ install_constant_lookup_hook(const: sym)
32
+ end
33
+
34
+ # Drawback is this will also create methods for modules and classes hanging
35
+ # from `base` namespace. Unfortunatelly, we need to go with them, because
36
+ # it might happen a constant points to one of them to implement some behaviour.
37
+ def install_constant_lookup_hooks(base = self)
38
+ # Only method-hook constants that are not inherited,
39
+ # nor delegated to the decorated class.
40
+ # This constraint ensure that we don't overpopulate with hook methods,
41
+ # and that the lookup respects the order in which the decorators are
42
+ # wrapped (i.e. Sugar.new(Milk.new(Coffe.new)) < Sugar(Milk(Coffee)) ).
43
+ base.constants(false, delegated: false).each do |const|
44
+ install_constant_lookup_hook(base, const: const)
45
+ end
46
+ end
47
+
48
+ def install_constant_lookup_hook(base = self, const:)
49
+ meth_name = underscore(const).to_sym
50
+ return if instance_method?(base, name: meth_name, include_private: true)
51
+
52
+ method_def = proc do |*args, **kargs, &block|
53
+ return super(*args, **kargs, &block) if defined?(super)
54
+
55
+ base.const_get(const)
56
+ end
57
+
58
+ base.define_method(meth_name, &method_def)
59
+ end
60
+
61
+ # @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore
62
+ def underscore(sym)
63
+ return super if defined?(super)
64
+ return sym.to_s.dup unless /[A-Z-]|::/.match?(sym)
65
+
66
+ word = sym.to_s.gsub('::', '/')
67
+ word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
68
+ word.tr!('-', '_')
69
+ word.downcase!
70
+ word
71
+ end
72
+
73
+ def instance_method?(base = self, name:, include_private: false)
74
+ return true if base.method_defined?(name)
75
+ return false unless include_private
76
+
77
+ base.private_instance_methods.include?(name.to_sym)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,84 @@
1
+ module Eco::Language::Delegation
2
+ module DelegatedClass
3
+ class << self
4
+ private
5
+
6
+ def included(base)
7
+ super
8
+
9
+ base.extend(ClassMethods)
10
+ base.send :patch_class_compare
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def delegated_class(klass = nil)
16
+ return @delegated_class if klass.nil?
17
+
18
+ @delegated_class = klass
19
+ end
20
+
21
+ private
22
+
23
+ def inherited(subclass)
24
+ super
25
+
26
+ subclass.instance_variable_set(:@delegated_class, @delegated_class)
27
+ end
28
+
29
+ def patch_class_compare(base = self)
30
+ %i[< <= ==].each do |op|
31
+ base.singleton_class.define_method(op) do |other|
32
+ return super(other) unless delegated_class
33
+ return true if super(other)
34
+
35
+ delegated_class.send(op, other)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Instance methods
42
+
43
+ # It allows to chain delegators, when the first parameter is an
44
+ # instance of `delegated_class`.
45
+ # @note it also allows to create an instance of `delegated_class`
46
+ # on initialization.
47
+ def initialize(*args, **kargs, &block)
48
+ obj = args.first
49
+ obj = new(*args, **kargs, &block) unless of_kind?(obj)
50
+
51
+ super(obj)
52
+ end
53
+
54
+ def delegated_class
55
+ self.class.delegated_class
56
+ end
57
+
58
+ def new(...)
59
+ delegated_class.new(...)
60
+ end
61
+
62
+ def of_kind?(obj)
63
+ return true if obj.is_a?(delegated_class)
64
+ return false unless obj.is_a(Delegator)
65
+ return true if obj.__get_obj__.is_a?(delegated_class)
66
+
67
+ false
68
+ end
69
+
70
+ def is_a?(base)
71
+ return true if super
72
+ return false unless delegated_class
73
+ return true if delegated_class <= base
74
+
75
+ false
76
+ end
77
+
78
+ def instance_of?(base)
79
+ return true if base == delegated_class
80
+
81
+ super
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,8 @@
1
+ module Eco::Language
2
+ module Delegation
3
+ end
4
+ end
5
+
6
+ require_relative 'delegation/delegated_class'
7
+ require_relative 'delegation/const_delegator'
8
+ require_relative 'delegation/const_lookup_hooks'
@@ -9,6 +9,7 @@ module Eco
9
9
 
10
10
  def when_inherited(&block)
11
11
  return @when_inherited unless block_given?
12
+
12
13
  @when_inherited = block
13
14
  end
14
15
  end
@@ -8,6 +8,7 @@ module Eco
8
8
 
9
9
  def method_missing(method_name, *args, **kargs, &block)
10
10
  super unless (receiver = object_missing_delegated_to)
11
+
11
12
  receiver.send(method_name, *args, **kargs, &block)
12
13
  end
13
14