ndr_import 11.3.0 → 11.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dd3ec74b568c0429492ded5083edf0eaf31a0a6c7849945f7fa69dbe0e43a49
4
- data.tar.gz: 0f845a90d21901350122a6d41437c8ebe53e1eaa49694af76b38811210280804
3
+ metadata.gz: 81d7c1189af2322610f0e094c3b469dc187a2738d4e97edfd0a03a2f5bec079a
4
+ data.tar.gz: 5a2553b6bbe30e6b74142395abde887d02298a268e893adb49674effb6047e31
5
5
  SHA512:
6
- metadata.gz: 26668128f4fa8a7165d50ee39e729f5d5a63b704c3db41a5cab1be8df02ea38bdeb7cd69494ef62656c22c7b8bd4aea2c94ee839019accff59a28f2b1452065c
7
- data.tar.gz: 6b09bbb5ae408a1ca9d79e74e6c1de3f2b7a1d3597e67a02207e20dc83e18a24bdd8e6f2117f56d781cff855dd14495a686ecfb4589dc5af4388ffc13a8f3d03
6
+ metadata.gz: c7ce3014831428ab2e0c151af76aa45b0b6f04f6c61232d170a2c88952b3a20c4e8a7e48631cf0f6776da137371d709dc3de8cbe25781083861f218676c80def
7
+ data.tar.gz: 76839142c01dcab91c21e69d6f96b36de761f89e71621ad4f6a86c9e11becf029b358e30aabbc628590763f6b66f018189c587fe1f9b139858bdaf4df7d1d6f1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
- * no unreleased changes
2
+ * no unreleased changes *
3
+
4
+ ## 11.3.1/ 2025-09-02
5
+ ### Enhancement
6
+ * Performance improvements for XML mask mappings
3
7
 
4
8
  ## 11.3.0/ 2025-02-11
5
9
  ### Fixed
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  # This stores the current version of the NdrImport gem
3
3
  module NdrImport
4
- VERSION = '11.3.0'
4
+ VERSION = '11.3.1'
5
5
  end
@@ -4,70 +4,156 @@ module NdrImport
4
4
  # Overriding the NdrImport::Table method to avoid memoizing. This by design, column mappings
5
5
  # can change if new mappings are added on the fly where repeating sections are present
6
6
  class MaskedMappings
7
- attr_accessor :klass, :augmented_columns
7
+ DO_NOT_CAPTURE_MAPPING = { 'do_not_capture' => true }.freeze
8
+ KLASS_KEY = 'klass'.freeze
9
+ COLUMN_KEY = 'column'.freeze
10
+ STANDARD_MAPPING_KEY = 'standard_mapping'.freeze
11
+ DO_NOT_CAPTURE_KEY = 'do_not_capture'.freeze
12
+ XML_CELL_KEY = 'xml_cell'.freeze
13
+ KEEP_KLASS_KEY = 'keep_klass'.freeze
14
+
15
+ # Pre-compiled regex for numbered variants
16
+ NUMBERED_VARIANT_PATTERN = /#\d+\z/
17
+
18
+ attr_reader :klass, :augmented_columns
8
19
 
9
20
  def initialize(klass, augmented_columns)
10
21
  @klass = klass
11
22
  @augmented_columns = augmented_columns
23
+ @column_count = augmented_columns.size
24
+ @has_klass = !klass.nil?
25
+
26
+ freeze
12
27
  end
13
28
 
14
29
  def call
15
- return { klass => augmented_columns } if klass.present?
16
-
17
- masked_mappings = column_level_klass_masked_mappings
18
-
19
- augmented_masked_mappings = masked_mappings
20
- # Remove any masked klasses where additional columns mappings
21
- # have been added for repeated sections
22
- # e.g. SomeTestKlass column mappings are not needed if SomeTestKlass#1
23
- # have been added
24
- masked_mappings.each do |masked_key, columns|
25
- # There may be occasions where the e.g. SomeTestKlass should be kept,
26
- # This can be flagged in the one the klass's column mappings
27
- next if columns.any? { |column| column.dig('xml_cell', 'keep_klass') }
28
-
29
- if masked_mappings.keys.any? { |key| key =~ /\A#{masked_key}#\d+\z/ }
30
- augmented_masked_mappings.delete(masked_key)
30
+ return { @klass => @augmented_columns } if @has_klass
31
+
32
+ masked_mappings = build_masked_mappings
33
+ remove_superseded_base_klasses(masked_mappings)
34
+ end
35
+
36
+ private
37
+
38
+ def build_masked_mappings
39
+ # Pre-validate and extract all klasses in one pass
40
+ all_klasses_set, klassless_column_names = extract_klasses_and_validate
41
+
42
+ raise "Missing klass for column(s): #{klassless_column_names.join(', ')}" unless klassless_column_names.empty?
43
+
44
+ all_klasses_array = all_klasses_set.to_a
45
+
46
+ # Pre-allocate result hash with exact size
47
+ result = Hash.new(all_klasses_array.size)
48
+
49
+ all_klasses_array.each do |current_klass|
50
+ result[current_klass] = mask_mappings_for_klass(current_klass)
51
+ end
52
+
53
+ result
54
+ end
55
+
56
+ def extract_klasses_and_validate
57
+ klasses_set = Set.new
58
+ klassless_column_names = []
59
+
60
+ @augmented_columns.each do |mapping|
61
+ mapping_klass = mapping[KLASS_KEY]
62
+
63
+ if mapping_klass.nil?
64
+ # Only collect klassless mappings that aren't marked as do_not_capture
65
+ klassless_column_names << column_name_from(mapping) unless mapping[DO_NOT_CAPTURE_KEY]
66
+ elsif mapping_klass.is_a?(Array)
67
+ klasses_set.merge(mapping_klass.compact)
68
+ else
69
+ klasses_set.add(mapping_klass)
31
70
  end
32
71
  end
33
72
 
34
- augmented_masked_mappings
73
+ [klasses_set, klassless_column_names]
35
74
  end
36
75
 
37
- private
76
+ def column_name_from(mapping)
77
+ mapping[COLUMN_KEY] || mapping[STANDARD_MAPPING_KEY]
78
+ end
38
79
 
39
- # This method duplicates the mappings and applies a do_not_capture mask to those that do not
40
- # relate to this klass, returning the masked mappings
41
- def mask_mappings_by_klass(klass)
42
- augmented_columns.deep_dup.map do |mapping|
43
- Array(mapping['klass']).flatten.include?(klass) ? mapping : { 'do_not_capture' => true }
80
+ def mask_mappings_for_klass(target_klass)
81
+ # Pre-allocate array with exact size
82
+ result = Array.new(@column_count)
83
+
84
+ # Single pass with index tracking
85
+ @augmented_columns.each_with_index do |mapping, index|
86
+ result[index] = if mapping_applies_to_klass?(mapping, target_klass)
87
+ mapping.deep_dup
88
+ else
89
+ DO_NOT_CAPTURE_MAPPING
90
+ end
91
+ end
92
+
93
+ result
94
+ end
95
+
96
+ def mapping_applies_to_klass?(mapping, target_klass)
97
+ mapping_klass = mapping[KLASS_KEY]
98
+ return false unless mapping_klass
99
+
100
+ # Optimized type checking and inclusion
101
+ case mapping_klass
102
+ when Array
103
+ mapping_klass.include?(target_klass)
104
+ when String
105
+ mapping_klass == target_klass
106
+ else
107
+ false
44
108
  end
45
109
  end
46
110
 
47
- def column_level_klass_masked_mappings
48
- ensure_mappings_define_klass
111
+ def remove_superseded_base_klasses(masked_mappings)
112
+ return masked_mappings if masked_mappings.size <= 1
113
+
114
+ # Pre-build numbered variants lookup for O(1) access
115
+ numbered_klasses = build_numbered_klasses_lookup(masked_mappings.keys)
116
+ return masked_mappings if numbered_klasses.empty?
117
+
118
+ klasses_to_keep = compute_klasses_to_keep(masked_mappings)
49
119
 
50
- # Loop through each klass
51
- masked_mappings = {}
52
- augmented_columns.pluck('klass').flatten.compact.uniq.each do |klass|
53
- # Do not capture fields that relate to other klasses
54
- masked_mappings[klass] = mask_mappings_by_klass(klass)
120
+ masked_mappings.select do |klass, _columns|
121
+ klasses_to_keep.include?(klass) || numbered_klasses.exclude?(klass)
55
122
  end
56
- masked_mappings
57
123
  end
58
124
 
59
- # This method ensures that every column mapping defines a klass (unless it is a column that
60
- # we do not capture). It is only used where a table level klass is not defined.
61
- def ensure_mappings_define_klass
62
- klassless_mappings = augmented_columns.
63
- select { |mapping| mapping.nil? || mapping['klass'].nil? }.
64
- reject { |mapping| mapping['do_not_capture'] }.
65
- map { |mapping| mapping['column'] || mapping['standard_mapping'] }
125
+ def build_numbered_klasses_lookup(klass_keys)
126
+ numbered_klasses = Set.new
66
127
 
67
- return if klassless_mappings.empty?
128
+ klass_keys.each do |key|
129
+ next unless key.match?(NUMBERED_VARIANT_PATTERN)
130
+
131
+ # Extract base klass name (everything before #)
132
+ base_klass = key.split(NUMBERED_VARIANT_PATTERN, 2).first
133
+ numbered_klasses.add(base_klass)
134
+ end
135
+
136
+ numbered_klasses
137
+ end
138
+
139
+ def compute_klasses_to_keep(masked_mappings)
140
+ klasses_to_keep = Set.new
141
+
142
+ masked_mappings.each do |klass, columns|
143
+ klasses_to_keep.add(klass) if should_keep_base_klass?(columns)
144
+ end
145
+
146
+ klasses_to_keep
147
+ end
148
+
149
+ def should_keep_base_klass?(columns)
150
+ # Fast iteration with early termination
151
+ columns.each do |column|
152
+ xml_cell = column[XML_CELL_KEY]
153
+ return true if xml_cell && xml_cell[KEEP_KLASS_KEY]
154
+ end
68
155
 
69
- # All column mappings for the single item file require a klass definition.
70
- raise "Missing klass for column(s): #{klassless_mappings.to_sentence}"
156
+ false
71
157
  end
72
158
  end
73
159
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ndr_import
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.3.0
4
+ version: 11.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - NCRS Development Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-12 00:00:00.000000000 Z
11
+ date: 2025-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -479,7 +479,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
479
479
  - !ruby/object:Gem::Version
480
480
  version: '0'
481
481
  requirements: []
482
- rubygems_version: 3.2.3
482
+ rubygems_version: 3.4.19
483
483
  signing_key:
484
484
  specification_version: 4
485
485
  summary: NDR Import