eco-helpers 2.6.4 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +95 -0
  4. data/CHANGELOG.md +139 -2
  5. data/Rakefile +13 -7
  6. data/eco-helpers.gemspec +2 -2
  7. data/lib/eco/api/common/loaders/base.rb +2 -2
  8. data/lib/eco/api/common/loaders/case_base.rb +1 -1
  9. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +5 -5
  10. data/lib/eco/api/common/loaders/error_handler.rb +8 -5
  11. data/lib/eco/api/common/loaders/parser.rb +44 -22
  12. data/lib/eco/api/common/loaders/policy.rb +6 -4
  13. data/lib/eco/api/common/loaders/use_case.rb +13 -7
  14. data/lib/eco/api/common/people/base_parser.rb +0 -2
  15. data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +0 -1
  16. data/lib/eco/api/common/people/default_parsers/csv_parser.rb +1 -1
  17. data/lib/eco/api/common/people/default_parsers/date_parser.rb +64 -12
  18. data/lib/eco/api/common/people/default_parsers/freemium_parser.rb +0 -1
  19. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +13 -5
  20. data/lib/eco/api/common/people/default_parsers/multi_parser.rb +0 -1
  21. data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +18 -5
  22. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +8 -8
  23. data/lib/eco/api/common/people/default_parsers/select_parser.rb +50 -26
  24. data/lib/eco/api/common/people/default_parsers/send_invites_parser.rb +6 -6
  25. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +9 -12
  26. data/lib/eco/api/common/people/default_parsers.rb +1 -12
  27. data/lib/eco/api/common/people/entries.rb +13 -13
  28. data/lib/eco/api/common/people/entry_factory.rb +76 -45
  29. data/lib/eco/api/common/people/person_attribute_parser.rb +8 -12
  30. data/lib/eco/api/common/people/person_entry.rb +86 -75
  31. data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +60 -44
  32. data/lib/eco/api/common/people/person_factory.rb +30 -22
  33. data/lib/eco/api/common/people/person_modifier.rb +11 -13
  34. data/lib/eco/api/common/people/person_parser.rb +101 -39
  35. data/lib/eco/api/common/people/supervisor_helpers.rb +25 -26
  36. data/lib/eco/api/common/session/base_session.rb +9 -9
  37. data/lib/eco/api/common/session/environment.rb +7 -5
  38. data/lib/eco/api/common/session/sftp.rb +59 -32
  39. data/lib/eco/api/common/version_patches/exception.rb +11 -13
  40. data/lib/eco/api/error.rb +32 -20
  41. data/lib/eco/api/organization/node_classifications.rb +82 -0
  42. data/lib/eco/api/organization/policy_groups.rb +4 -6
  43. data/lib/eco/api/organization/tag_tree.rb +169 -93
  44. data/lib/eco/api/organization.rb +1 -0
  45. data/lib/eco/api/session/batch/job.rb +1 -1
  46. data/lib/eco/api/session/config/tagtree.rb +41 -23
  47. data/lib/eco/api/session/config/workflow.rb +113 -88
  48. data/lib/eco/api/session/config.rb +6 -0
  49. data/lib/eco/api/session.rb +51 -29
  50. data/lib/eco/api/usecases/base_io.rb +28 -25
  51. data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +7 -2
  52. data/lib/eco/api/usecases/default/locations/cli/tagtree_upload_cli.rb +21 -0
  53. data/lib/eco/api/usecases/default/locations/csv_to_tree_case.rb +3 -3
  54. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +54 -23
  55. data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +87 -0
  56. data/lib/eco/api/usecases/default/locations.rb +1 -0
  57. data/lib/eco/api/usecases/default/people/analyse_people_case.rb +60 -56
  58. data/lib/eco/api/usecases/default/people/change_email_case.rb +8 -9
  59. data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +13 -11
  60. data/lib/eco/api/usecases/default/people/clear_abilities_case.rb +2 -2
  61. data/lib/eco/api/usecases/default/people/org_data_convert_case.rb +25 -27
  62. data/lib/eco/api/usecases/default/people/refresh_case.rb +2 -2
  63. data/lib/eco/api/usecases/default/people/reinvite_trans_case.rb +1 -1
  64. data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +0 -1
  65. data/lib/eco/api/usecases/default/people/restore_db_case.rb +39 -34
  66. data/lib/eco/api/usecases/default/people/set_default_tag_case.rb +19 -15
  67. data/lib/eco/api/usecases/default/people/supers_cyclic_identify_case.rb +16 -12
  68. data/lib/eco/api/usecases/default_cases/hris_case.rb +17 -15
  69. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +30 -16
  70. data/lib/eco/api/usecases/graphql/base.rb +5 -3
  71. data/lib/eco/api/usecases/graphql/helpers/base/case_env.rb +4 -1
  72. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +14 -0
  73. data/lib/eco/api/usecases/graphql/helpers/base.rb +5 -4
  74. data/lib/eco/api/usecases/graphql/helpers/location/base/classifications_parser.rb +60 -0
  75. data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +72 -0
  76. data/lib/eco/api/usecases/graphql/helpers/location/base.rb +25 -59
  77. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +59 -0
  78. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/compare.rb +49 -0
  79. data/lib/eco/api/usecases/graphql/helpers/location/command/diff.rb +11 -0
  80. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +46 -0
  81. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +23 -0
  82. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +65 -0
  83. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +49 -0
  84. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +119 -0
  85. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +59 -0
  86. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages.rb +82 -0
  87. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs.rb +20 -0
  88. data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +84 -0
  89. data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +4 -4
  90. data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +24 -12
  91. data/lib/eco/api/usecases/graphql/helpers/location/command.rb +21 -24
  92. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +1 -1
  93. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +10 -11
  94. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +8 -9
  95. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +41 -12
  96. data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +11 -80
  97. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +89 -0
  98. data/lib/eco/api/usecases/graphql/samples/location/command/service.rb +6 -0
  99. data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +89 -0
  100. data/lib/eco/api/usecases/graphql/samples/location/command.rb +3 -0
  101. data/lib/eco/api/usecases/graphql/samples/location/service/base.rb +9 -0
  102. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +18 -0
  103. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +53 -0
  104. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/classifications.rb +34 -0
  105. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/helpers.rb +28 -0
  106. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +46 -0
  107. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible.rb +38 -0
  108. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +105 -0
  109. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/discarded.rb +16 -0
  110. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +15 -0
  111. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/node_attr_maps.rb +22 -0
  112. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/parser.rb +45 -0
  113. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter.rb +36 -0
  114. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/output.rb +56 -0
  115. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list.rb +41 -0
  116. data/lib/eco/api/usecases/graphql/samples/location/service.rb +8 -0
  117. data/lib/eco/api/usecases/graphql/samples/location.rb +1 -0
  118. data/lib/eco/api/usecases/graphql/utils/sftp.rb +96 -36
  119. data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +8 -6
  120. data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +4 -3
  121. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +39 -25
  122. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +13 -15
  123. data/lib/eco/api/usecases/ooze_samples/helpers/filters.rb +50 -21
  124. data/lib/eco/api/usecases/ooze_samples/helpers/ooze_handlers.rb +21 -11
  125. data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +2 -0
  126. data/lib/eco/api/usecases/ooze_samples/helpers/shortcuts.rb +49 -43
  127. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +17 -19
  128. data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +48 -43
  129. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +33 -34
  130. data/lib/eco/api/usecases/ooze_samples/target_oozes_update_case.rb +8 -10
  131. data/lib/eco/api/usecases.rb +0 -1
  132. data/lib/eco/cli/config/use_cases.rb +31 -29
  133. data/lib/eco/cli_default/workflow.rb +13 -14
  134. data/lib/eco/csv/table.rb +34 -25
  135. data/lib/eco/data/hashes/array_diff.rb +24 -35
  136. data/lib/eco/data/hashes/diff_result/meta.rb +131 -0
  137. data/lib/eco/data/hashes/diff_result.rb +65 -57
  138. data/lib/eco/data/hashes/sanke_camel_indifferent_access.rb +278 -0
  139. data/lib/eco/data/hashes.rb +1 -1
  140. data/lib/eco/data/locations/convert.rb +1 -1
  141. data/lib/eco/data/locations/node_base/csv_convert.rb +19 -9
  142. data/lib/eco/data/locations/node_base/parsing.rb +4 -2
  143. data/lib/eco/data/locations/node_base/treeify.rb +149 -132
  144. data/lib/eco/data/locations/node_base.rb +15 -4
  145. data/lib/eco/data/locations/node_diff/accessors.rb +13 -5
  146. data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +101 -0
  147. data/lib/eco/data/locations/node_diff/nodes_diff/diffs_tree.rb +99 -0
  148. data/lib/eco/data/locations/node_diff/{selectors.rb → nodes_diff/selectors.rb} +1 -1
  149. data/lib/eco/data/locations/node_diff/nodes_diff.rb +50 -35
  150. data/lib/eco/data/locations/node_diff.rb +45 -17
  151. data/lib/eco/data/locations/node_level/parsing.rb +15 -21
  152. data/lib/eco/data/locations/node_level.rb +66 -22
  153. data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
  154. data/lib/eco/data/locations/node_plain.rb +60 -7
  155. data/lib/eco/data/strings/camel_case.rb +35 -0
  156. data/lib/eco/data/strings/snake_case.rb +18 -0
  157. data/lib/eco/data/strings.rb +8 -0
  158. data/lib/eco/data.rb +1 -0
  159. data/lib/eco/language/auxiliar_logger.rb +7 -5
  160. data/lib/eco/language/methods/call_detector.rb +11 -0
  161. data/lib/eco/language/methods/dsl_able.rb +7 -1
  162. data/lib/eco/language/methods.rb +2 -1
  163. data/lib/eco/language/models/collection.rb +23 -25
  164. data/lib/eco/language/models/parser_serializer.rb +24 -5
  165. data/lib/eco/version.rb +1 -1
  166. data/lib/eco-helpers.rb +0 -1
  167. metadata +52 -7
  168. data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,131 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ class DiffResult
5
+ module Meta
6
+ class << self
7
+ def included(base)
8
+ super(base)
9
+ base.extend Eco::Language::Models::ClassHelpers
10
+ base.extend ClassMethods
11
+ base.inheritable_class_vars :key, :compared_attrs, :case_sensitive
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ # @param value [String, NilClass]
17
+ # @return [String] the attribute that is key of the node diff elements.
18
+ def key(value = nil)
19
+ return @key unless value
20
+ @key = value.to_s
21
+ end
22
+
23
+ # @return [Boolean] is there a `key` attribute defined?
24
+ def key?
25
+ !!@key
26
+ end
27
+
28
+ # @param attrs [Array<Symbol>, Array<String>]
29
+ # @return [Array<String>] the comparable attributes
30
+ def compare(*attrs, when_present: false)
31
+ if when_present
32
+ compared_attrs_when_present.push(*attrs.map(&:to_s)).uniq!
33
+ compared_attrs_when_present
34
+ else
35
+ compared_attrs.push(*attrs.map(&:to_s)).uniq!
36
+ compared_attrs
37
+ end
38
+ end
39
+
40
+ # Whether or not the diff calc of a node should be done case sensitive or insensitive.
41
+ def case_sensitive(value = nil)
42
+ @case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
43
+ return @case_sensitive unless value
44
+ @case_sensitive = !!value
45
+ end
46
+
47
+ # @return [Boolean] are comparisons of values done case sensitive?
48
+ def case_sensitive?
49
+ !!@case_sensitive
50
+ end
51
+
52
+ # @return [Array<String>] the comparable attributes
53
+ def compared_attrs
54
+ @compared_attrs ||= []
55
+ end
56
+
57
+ # Same as `compared_attrs` but they only get compared when present
58
+ # in both data sources
59
+ # @note if present in one source but not in another,
60
+ # that attribute will NOT generate a diff.
61
+ def compared_attrs_when_present
62
+ @compared_attrs_when_present ||= []
63
+ end
64
+
65
+ def all_compared_attrs
66
+ compared_attrs | compared_attrs_when_present
67
+ end
68
+
69
+ def compared_attr_when_present?(attr)
70
+ compared_attrs_when_present.include?(attr)
71
+ end
72
+ end
73
+
74
+ def key
75
+ self.class.key.dup
76
+ end
77
+
78
+ def case_sensitive?
79
+ self.class.case_sensitive?
80
+ end
81
+
82
+ def all_compared_attrs
83
+ self.class.all_compared_attrs.dup.compact.map(&:to_s)
84
+ end
85
+
86
+ private
87
+
88
+ # @return [Boolean] whether `val1` is equal to `val2`
89
+ def eq?(val_1, val_2)
90
+ return eq_ary?(val_1, val_2) if [val_1, val_2].any? {|v| v.is_a?(Array)}
91
+ return true if val_1 == val_2
92
+ return false if case_sensitive?
93
+ return false unless val_2 && val_1
94
+ val_1.upcase == val_2.upcase
95
+ end
96
+
97
+ # Compares two arrays
98
+ # @note
99
+ # 1. Order does NOT matter
100
+ # 2. Repeated elements are treated as one
101
+ def eq_ary?(val_1, val_2)
102
+ return false unless [val_1, val_2].all? {|v| v.is_a?(Array)}
103
+ return true if val_1 == val_2
104
+ v_1 = case_sensitive?? val_1 : val_1.map {|v| v&.upcase}
105
+ v_2 = case_sensitive?? val_2 : val_2.map {|v| v&.upcase}
106
+ joined = (v_1 | v_2)
107
+ (joined & v_1) == (joined & v_2)
108
+ end
109
+
110
+ # Hash access
111
+ # @note DSL to be able to modify behaviour
112
+ # (i.e. indifferent access for camel and snake case keys)
113
+ def get_attr(src, key)
114
+ return nil unless src
115
+ src[key.to_s]
116
+ end
117
+
118
+ def slice_attrs(src, *keys)
119
+ return nil unless src
120
+ src.slice(*keys)
121
+ end
122
+
123
+ def key_present?(src, key)
124
+ return false unless src
125
+ src.key?(key)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -1,53 +1,68 @@
1
+ require_relative 'diff_result/meta'
2
+
1
3
  module Eco
2
4
  module Data
3
5
  module Hashes
4
6
  class DiffResult
5
- extend Eco::Language::Models::ClassHelpers
6
- include Eco::Data::Hashes::DiffMeta
7
-
8
- inheritable_class_vars :key, :compared_attrs, :case_sensitive
7
+ include Eco::Data::Hashes::DiffResult::Meta
9
8
 
10
- attr_reader :src1, :src2
9
+ attr_reader :src_1, :src_2
11
10
 
12
- def initialize(src1, src2)
13
- @src1 = src1
14
- @src2 = src2
11
+ def initialize(src_1, src_2)
12
+ @src_1 = src_1
13
+ @src_2 = src_2
15
14
  end
16
15
 
17
- def key
18
- self.class.key
16
+ # @return [Value] the current value of `attr` (in `src_2`)
17
+ def attr(attr)
18
+ get_attr(src_2, attr)
19
19
  end
20
20
 
21
- def case_sensitive?
22
- self.class.case_sensitive?
21
+ # @return [Value] the previous value of `attr` (in `src_1`)
22
+ def attr_prev(attr)
23
+ get_attr(src_1, attr)
23
24
  end
24
25
 
25
- def dup(src1: nil, src2: nil)
26
- src1 ||= self.src1
27
- src2 ||= self.src2
28
- self.class.new(src1.dup, src2.dup)
26
+ # Deduplication to prevent mutability when manipulation is required.
27
+ def dup(src_1: nil, src_2: nil)
28
+ src_1 ||= self.src_1
29
+ src_2 ||= self.src_2
30
+ self.class.new(src_1.dup, src_2.dup)
29
31
  end
30
32
 
31
33
  def new?
32
- !src1 && !!src2
34
+ src_2 && !src_1
33
35
  end
34
36
 
35
37
  def del?
36
- !!src1 && !src2
38
+ src_1 && !src_2
37
39
  end
38
40
 
39
41
  def update?
40
42
  !new? && !del? && diff?
41
43
  end
42
44
 
45
+ # Uniq access point to identify attrs that have changed.
46
+ # @note when is `new?` or to be deleted (`del?`), there's nothing to compare.
47
+ # @return [Array<Symbol>] hash with the list of attrs that are different
48
+ # between `src_1` and `src_2`.
49
+ def diff_attrs
50
+ return (@diff_attrs = []) if new? || del?
51
+ @diff_attrs ||= all_compared_attrs.each_with_object([]) do |kattr, out|
52
+ next unless comparable_attr?(kattr)
53
+ out << kattr unless eq?(attr_prev(kattr), attr(kattr))
54
+ end
55
+ end
56
+
43
57
  # @note `diff_attrs` may not include the `key` attribute
44
58
  # This is always included via `new?` (new key value) and `del?` (missing key value)
45
59
  # @return [Boolean] was there any change?
46
60
  def diff?
47
- new? || del? || !diff_attrs.empty?
61
+ new? || del? || diff_attrs.any?
48
62
  end
49
63
 
50
- # Is the `key` attr value changing?
64
+ # Is the `key` attr value updated?
65
+ # @note that `new?` and `del?` won't be considered as key's change
51
66
  def key?
52
67
  !(new? || del?) && diff_attr?(key)
53
68
  end
@@ -59,54 +74,47 @@ module Eco
59
74
  diff_attrs.include?(attr.to_s)
60
75
  end
61
76
 
62
- # @return [Value] the current value of `attr` (in `src2`)
63
- def attr(attr)
64
- return nil unless src2
65
- src2[attr.to_s]
66
- end
67
-
68
- # @return [Value] the previous value of `attr` (in `src1`)
69
- def attr_prev(attr)
70
- return nil unless src1
71
- src1[attr.to_s]
72
- end
73
-
74
77
  # @note the `key` attribute will always be added (even if there's no change)
75
- # @return [Hash] hash with the differences as per `src2`
78
+ # @return [Hash] hash with the differences as per `src_2`
76
79
  def diff_hash
77
- target_attrs = [key] | compared_attrs
78
- return src2.slice(*target_attrs) if new?
79
- return src1.slice(key) if del?
80
- src2.slice(key, *diff_attrs)
80
+ target_attrs = [key] | all_compared_attrs
81
+ return slice_attrs(src_2, *target_attrs) if new?
82
+ return slice_attrs(src_1, key) if del?
83
+ slice_attrs(src_2, key, *diff_attrs)
81
84
  end
82
85
 
83
- # @return [Array<Symbol>] hash with the differences as per `src2`
84
- def diff_attrs
85
- @diff_attrs ||= comparable_attrs.each_with_object([]) do |attr, out|
86
- out << attr unless eq?(src1[attr], src2[attr])
86
+ # Uniq access point to scope if an `attr` is in the scope of the diff compare.
87
+ # Set of attributes that are general target to identify differences
88
+ # between both sources.
89
+ # @note When the class `all_compared_attrs` has not been deefined,
90
+ # it uses `all_source_keys`
91
+ # @return [Array<String>] the set of attributes that are comparable in this class.
92
+ def all_compared_attrs
93
+ super().map(&:to_s).uniq.tap do |attrs|
94
+ return all_source_keys unless attrs.any?
87
95
  end
88
96
  end
89
97
 
90
- # @return [Boolean] whether `val1` is equal to `val2`
91
- def eq?(val1, val2)
92
- return true if val1 == val2
93
- return false if case_sensitive?
94
- return false if !val2 || !val1
95
- val1.upcase == val2.upcase
98
+ # All the keys that the data comes with
99
+ def all_source_keys
100
+ (src_1&.keys || []) & (src_2&.keys || [])
96
101
  end
97
102
 
98
- # @note when is `new?` or to be deleted (`del?`), it returns empty array.
99
- # @return [Array<String>] the set of attributes that are comparable in this instance.
100
- def comparable_attrs
101
- return [] if new? || del?
102
- compared_attrs
103
+ private
104
+
105
+ # Refinement over `all_compared_attrs`, provided that those that must
106
+ # be comparable only **when present** in both, are filtered out when
107
+ # that is not the case.
108
+ def comparable_attr?(attr)
109
+ attr = attr.to_s
110
+ return false unless all_compared_attrs.include?(attr)
111
+ return true unless self.class.compared_attr_when_present?(attr)
112
+ key_present_in_both?(attr)
103
113
  end
104
114
 
105
- # @return [Array<String>] the set of attributes that are comparable in this class.
106
- def compared_attrs
107
- comp_attrs = self.class.compared_attrs.map(&:to_s).uniq
108
- return comp_attrs unless comp_attrs.empty?
109
- (src1&.keys || []) & (src2&.keys || [])
115
+ # Is the key `attr` present in both sources?
116
+ def key_present_in_both?(attr)
117
+ key_present?(src_1, attr) && key_present?(src_2, attr)
110
118
  end
111
119
  end
112
120
  end
@@ -0,0 +1,278 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ module SnakeCamelIndifferentAccess
5
+ include Eco::Data::Strings::SnakeCase
6
+ include Eco::Data::Strings::CamelCase
7
+
8
+ private
9
+
10
+ # options are: `:snake_case` and `:camel_case`
11
+ def hash_isca_preferred
12
+ @hash_isca_preferred ||= :snake_case
13
+ end
14
+
15
+ # Sets `value` for `key`. The end result is that get on `key` gives `value`,
16
+ # no matter the original case form of `key`. This might entail to resolve
17
+ # the `key` if its in its multiple case forms.
18
+ # @note if an internal merge resolve of the `key` happens, it's done in the
19
+ # preferred case form.
20
+ # @note if once resolved the internal merge, only the preferred case form of
21
+ # the key remains, that key form will be used to do the `set` operation,
22
+ # no matter the original case form of the `key` argument.
23
+ # @param keep_case [Boolean] whether only the preferred case form of key
24
+ # should be used or rather try to keep its case.
25
+ def hash_isca_set(hash, key, value, prefer: nil, keep_case: false, residual: {})
26
+ # (pp hash) if key == 'barCamel'
27
+ hash_isca_resolve_key!(hash, key, prefer: prefer, residual: residual)
28
+ # (pp hash) if key == 'barCamel'
29
+
30
+ target_key = hash_isca_to_correct_key(key, keep_case: keep_case, prefer: prefer)
31
+
32
+ # puts "potential_target_key: '#{key}' => '#{target_key}'" if key == 'barCamel'
33
+
34
+ if keep_case
35
+ # Spot the existing target key
36
+ ext_keys = hash_isca_existing_correct_keys(hash, key, prefer: prefer)
37
+ target_key = ext_keys.first unless ext_keys.empty? || ext_keys.include?(target_key)
38
+ end
39
+
40
+ puts "final target_key: '#{key}' => '#{target_key}'" if key == 'barCamel'
41
+
42
+ hash[target_key] = value
43
+ hash_isca_resolve_key!(hash, key, prefer: prefer, residual: residual)
44
+
45
+ pp hash if key == 'barCamel'
46
+ puts "*" * 20 if key == 'barCamel'
47
+
48
+ value
49
+ end
50
+
51
+ # Retrieves the value of key `key`
52
+ # @note
53
+ # 1. when both forms of the key exist, it uses the preferred case form !!
54
+ # 2. when the key exists only in its form it uses it
55
+ # 3. when only the other form of the key is present it uses it
56
+ def hash_isca_get(value, key, prefer: nil)
57
+ hash = value.to_h
58
+ keys = hash_isca_double_key(key, prefer: prefer)
59
+ keys = keys.find_all {|k| hash.key?(k)}
60
+ return nil unless keys.any?
61
+ hash[keys.first]
62
+ end
63
+
64
+ # Slice generates a hash with `keys`, regardless the original case form
65
+ # of those keys in the source hash (value).
66
+ # @note values are retrieved as per `hash_isca_get` rules.
67
+ def hash_isca_slice(value, *keys, prefer: nil)
68
+ hash = value.to_h
69
+ values = keys.map do |key|
70
+ hash_isca_get(hash, key, prefer: prefer)
71
+ end
72
+ keys.zip(values).to_h
73
+ end
74
+
75
+ # Delegates to `hash_isca_get`
76
+ def hash_isca_values_at(value, *keys, prefer: nil)
77
+ hash = value.to_h
78
+ keys.map do |key|
79
+ hash_isca_get(hash, key, prefer: prefer)
80
+ end
81
+ end
82
+
83
+ # The values of the uniq keys, where among keys with multiple case form
84
+ # the preferred form case is used to retrieve the value.
85
+ # @note residual named arg was not added just to keep consistent its hash type.
86
+ def hash_isca_values(value, prefer: nil)
87
+ hash = value.to_h
88
+ hash.values_at(*hash_isca_uniq_keys(hash, prefer: prefer))
89
+ end
90
+
91
+ # Enumerator
92
+ def hash_isca_each(value, prefer: nil, residual: {}, &block)
93
+ return to_enum(:hash_isca_each, value, prefer: prefer, residual: residual) unless block_given?
94
+ hash = value.to_h
95
+ keys = hash_isca_uniq_keys(hash, prefer: prefer)
96
+ residual.merge!(hash.slice(*(hash.keys - keys)))
97
+ hash.slice(*keys).each(&block)
98
+ end
99
+
100
+ # @note due to simplicity and consistency, for matching merging keys
101
+ # it resolves internal key conflicts on both (source and other).
102
+ # This internal resolve is ONLY applied to common keys (between source and other).
103
+ def hash_isca_merge!(hash_1, value_2, prefer: nil, residual: {})
104
+ hash_1.tap do
105
+ hash_2 = hash_isca_internal_merge_resolve!(value_2.to_h.dup, residual: (res_2 = {}))
106
+ hash_isca_each(hash_2, prefer: prefer, residual: residual) do |key, value|
107
+ hash_isca_set(hash_1, key, value, prefer: prefer, keep_case: true, residual: residual)
108
+ end
109
+ residual.merge!(res_2)
110
+ end
111
+ end
112
+
113
+ def hash_isca_merge(value_1, value_2, prefer: nil, residual: {})
114
+ hash_isca_merge!(value_1.to_h.dup, value_2, prefer: prefer, residual: residual)
115
+ end
116
+
117
+ # Keys may not necessarily come in its camel or snake case version.
118
+ # This method ensures they are.
119
+ # @note modus operandi:
120
+ # 1. will preserve all the keys that were in a correct case form.
121
+ # 2. where it can't preserve, the preferred form will be used instead.
122
+ # 3. where merge conflicts exist, the value associatd with the key that
123
+ # was originally in the correc case form will prevail.
124
+ def hash_isca_correct_keys!(hash, prefer: nil, residual: {})
125
+ hash.dup.each do |key, _val|
126
+ ckey = hash_isca_to_correct_key(key, keep_case: true, prefer: prefer)
127
+ next if ckey == key
128
+ residual[key] = hash.delete(key) if hash.key?(ckey)
129
+ end
130
+ hash
131
+ end
132
+
133
+ # Same as `hash_isca_correct_keys!` but only for one single key.
134
+ def hash_isca_correct_key!(hash, key, prefer: nil, residual: {})
135
+ tkey = hash_isca_to_correct_key(key, keep_case: true, prefer: prefer)
136
+
137
+ hash.dup.each do |k_c, _val|
138
+ ckey = hash_isca_to_correct_key(k_c, keep_case: true, prefer: prefer)
139
+ next unless ckey == tkey
140
+ next if ckey == k_c
141
+ residual[k_c] = hash.delete(k_c) if hash.key?(ckey)
142
+ end
143
+ hash
144
+ end
145
+
146
+ # internal merge
147
+ def hash_isca_internal_merge_resolve!(hash, prefer: nil, residual: {})
148
+ hash_isca_correct_keys!(hash, prefer: prefer, residual: residual)
149
+ keep_keys = hash_isca_uniq_keys(hash, prefer: prefer)
150
+ hash.dup.each do |key, _v|
151
+ residual[key] = hash.delete(key) unless keep_keys.include?(key)
152
+ end
153
+ hash
154
+ end
155
+
156
+ # internal merge
157
+ def hash_isca_internal_merge_resolve(value, prefer: nil, residual: {})
158
+ hash_isca_internal_merge_resolve!(value.to_h.dup, prefer: prefer, residual: residual)
159
+ end
160
+
161
+ # it resolves a single key
162
+ def hash_isca_resolve_key!(hash, key, prefer: nil, residual: {})
163
+ hash_isca_correct_key!(hash, key, prefer: prefer, residual: residual)
164
+ return hash if hash_isca_uniq_key?(hash, key)
165
+ _pkey, rkey = hash_isca_double_key(key, prefer: prefer)
166
+ residual[rkey] = hash.delete(rkey)
167
+ hash
168
+ end
169
+
170
+ # all keys to camel case
171
+ def hash_isca_camelize_keys(value)
172
+ hash_isca_with_preferred_keys(value, prefer: :camel_case)
173
+ end
174
+
175
+ def hash_isca_camelize_keys!(hash)
176
+ hash_isca_with_preferred_keys!(hash, prefer: :camel_case)
177
+ end
178
+
179
+ # all keys to snake case
180
+ def hash_isca_snakeize_keys(value)
181
+ hash_isca_with_preferred_keys(value, prefer: :snake_case)
182
+ end
183
+
184
+ def hash_isca_snakeize_keys!(hash)
185
+ hash_isca_with_preferred_keys!(hash, prefer: :snake_case)
186
+ end
187
+
188
+ # Conerts keys to the `prefer` case form
189
+ # @note it first resolves with the default/general `hash_isca_preferred`,
190
+ # as all operations have been done up to now this way.
191
+ def hash_isca_with_preferred_keys!(hash, prefer: nil, residual: {})
192
+ hash_isca_internal_merge_resolve!(hash, residual: residual)
193
+ hash.dup.each do |k, _v|
194
+ key = hash_isca_to_preferred_key(k, prefer: prefer)
195
+ hash[key] = hash.delete(k)
196
+ end
197
+ hash
198
+ end
199
+
200
+ def hash_isca_with_preferred_keys(value, prefer: nil)
201
+ hash_isca_with_preferred_keys!(value.to_h.dup, prefer: prefer)
202
+ end
203
+
204
+ # @return [Array<String>] the resolved keys
205
+ def hash_isca_uniq_keys(value, prefer: nil)
206
+ hash = value.to_h
207
+ all_keys = hash.keys
208
+ dup_keys = all_keys.each_with_object({}) do |key, res|
209
+ keys = hash_isca_double_key(key, prefer: prefer)
210
+ next unless keys.all? {|k| hash.key?(k)}
211
+ next if res.key?(keys.first)
212
+ res[keys.first] = keys.last
213
+ end
214
+ all_keys - dup_keys.values
215
+ end
216
+
217
+ # @note if among the raw existing keys there were multiple keys for a given
218
+ # correct case version of `key`, they would be considered as one.
219
+ # @return [Boolean] `true` if the key is only in one form or not present.
220
+ # `false` if the key is in **multiple** forms
221
+ def hash_isca_uniq_key?(value, key)
222
+ hash_isca_existing_correct_keys(value, key).length <= 1
223
+ end
224
+
225
+ # For a given `key` it finds out what keys exist in its correct case form.
226
+ # @return [Array<String>] the existing correct keys.
227
+ def hash_isca_existing_correct_keys(value, key, prefer: nil)
228
+ hash = value.to_h
229
+ hash_isca_double_key(key, prefer: prefer).find_all do |k|
230
+ hash.key?(k)
231
+ end
232
+ end
233
+
234
+ def hash_isca_key?(value, key)
235
+ hash = value.to_h
236
+ hash_isca_double_key(key).any? {|k| hash.key?(k)}
237
+ end
238
+
239
+ def hash_isca_same_key?(k_1, k_2)
240
+ hash_isca_double_key(k_1) == hash_isca_double_key(k_2)
241
+ end
242
+
243
+ # @return [String] the preferred case form of the key
244
+ def hash_isca_to_preferred_key(key, prefer: nil)
245
+ hash_isca_double_key(key, prefer: prefer).first
246
+ end
247
+
248
+ # Spot the correct key. Ensures keys are in camel or snake case.
249
+ # @param keep_case [Boolean] whether only the preferred case form of key
250
+ # should be used or rather try to keep its case.
251
+ # @return [String] the target key
252
+ def hash_isca_to_correct_key(key, keep_case: true, prefer: nil)
253
+ pkey, okey = hash_isca_double_key(key, prefer: prefer)
254
+ return pkey unless keep_case
255
+ return pkey unless [pkey, okey].include?(key)
256
+ key
257
+ end
258
+
259
+ # @return [Boolean] whether `key` is already in snake or camel case form
260
+ def hash_isca_correct_key?(key)
261
+ hash_isca_double_key(key).any? {|k| k == key}
262
+ end
263
+
264
+ def hash_isca_double_key(key, prefer: nil)
265
+ return [key, key] unless key.is_a?(String)
266
+ return [snake_case(key), camel_case(key)] if hash_isca_prefer(prefer) == :snake_case
267
+ [camel_case(key), snake_case(key)]
268
+ end
269
+
270
+ def hash_isca_prefer(prefer = nil)
271
+ return hash_isca_preferred || :snake_case if prefer.nil?
272
+ return :camel_case unless prefer == :snake_case
273
+ :snake_case
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
@@ -5,6 +5,6 @@ module Eco
5
5
  end
6
6
  end
7
7
 
8
- require_relative 'hashes/diff_meta'
8
+ require_relative 'hashes/sanke_camel_indifferent_access'
9
9
  require_relative 'hashes/diff_result'
10
10
  require_relative 'hashes/array_diff'
@@ -76,7 +76,7 @@ module Eco::Data::Locations
76
76
  end
77
77
 
78
78
  # It logs a message from `yield` and appends a `pretty_inspect` on object.
79
- # @note it only works where `object` is `Enumberable`
79
+ # @note it only works where `object` is `Enumerable`
80
80
  def log_pretty_inspect(object, lev = :info)
81
81
  return unless object.is_a?(Enumerable)
82
82
  return if object.empty?
@@ -6,17 +6,21 @@ module Eco::Data::Locations::NodeBase
6
6
  Eco::API::Organization::TagTree
7
7
  end
8
8
 
9
- # @yield [Node] optional custom serializer
9
+ # @yield [node, json] optional custom serializer
10
+ # @yieldparam node [Node] the node that is being serialized
11
+ # @yieldparam json [Hash] the default serialization
10
12
  # @yieldreturn [Hash] the serialized Node
11
13
  # @param value [CSV::Table, Eco::API::Organization::TagTree]
12
14
  # @return [Array<Hash>] a plain list of hash nodes
13
15
  def hash_list(value, &block)
14
- return hash_list(org_tree(value)) if value.is_a?(::CSV::Table)
15
- return value.as_nodes_json if value.is_a?(tree_class)
16
+ return hash_list(org_tree(value), &block) if value.is_a?(::CSV::Table)
17
+ return value.as_nodes_json(&block) if value.is_a?(tree_class)
16
18
  raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
17
19
  end
18
20
 
19
- # @yield [Node] optional custom serializer
21
+ # @yield [node, json] optional custom serializer
22
+ # @yieldparam node [Node] the node that is being serialized
23
+ # @yieldparam json [Hash] the default serialization
20
24
  # @yieldreturn [Hash] the serialized Node
21
25
  # @param value [CSV::Table, Eco::API::Organization::TagTree]
22
26
  # @return [Array<Hash>] a hierarchical tree of hash nodes,
@@ -27,7 +31,9 @@ module Eco::Data::Locations::NodeBase
27
31
  raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
28
32
  end
29
33
 
30
- # @yield [Node] optional custom serializer
34
+ # @yield [node, json] optional custom serializer
35
+ # @yieldparam node [Node] the node that is being serialized
36
+ # @yieldparam json [Hash] the default serialization
31
37
  # @yieldreturn [Hash] the serialized Node
32
38
  # @param value [CSV::Table, Eco::API::Organization::TagTree]
33
39
  # @return [Eco::API::Organization::TagTree]
@@ -37,21 +43,25 @@ module Eco::Data::Locations::NodeBase
37
43
  raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
38
44
  end
39
45
 
40
- # @yield [Node] optional custom serializer
46
+ # @yield [node, json] optional custom serializer
47
+ # @yieldparam node [Node] the node that is being serialized
48
+ # @yieldparam json [Hash] the default serialization
41
49
  # @yieldreturn [Hash] the serialized Node
42
50
  # @return [CSV::Table] a table with L1 to Ln columns ready for dump to csv
43
- def csv_tree(value, encoding: 'utf-8', attrs: [:id], &block)
51
+ def csv_tree(value, encoding: 'utf-8', attrs: [:id], &block) # rubocop:disable Lint/UnusedMethodArgument
44
52
  Eco::CSV::Table.new(hash_tree_to_tree_csv(hash_tree(value, &block), attrs: attrs))
45
53
  end
46
54
 
47
55
  # @note it just converts to an organizational tagtree and uses a helper method.
48
- # @yield [Node] optional custom serializer
56
+ # @yield [node, json] optional custom serializer
57
+ # @yieldparam node [Node] the node that is being serialized
58
+ # @yieldparam json [Hash] the default serialization
49
59
  # @yieldreturn [Hash] the serialized Node
50
60
  # @param value [CSV::Table, Eco::API::Organization::TagTree]
51
61
  # @return [CSV::Table] a table with a list of nodes and their parents
52
62
  def csv_list(value, &block)
53
63
  value = org_tree(value, &block) unless value.is_a?(tree_class)
54
- Eco::CSV::Table.new(hash_list(value))
64
+ Eco::CSV::Table.new(hash_list(value, &block))
55
65
  end
56
66
  end
57
67
  end