Class: LcpRuby::Import::AutoMapper
- Inherits:
-
Object
- Object
- LcpRuby::Import::AutoMapper
- Defined in:
- lib/lcp_ruby/import/auto_mapper.rb
Overview
Automatically maps source CSV/XLSX column headers to target model field names.
Class Method Summary collapse
-
.auto_map(source_headers, target_fields) ⇒ Hash
{ “Source Column” => “field_name”, … } for matched columns.
Instance Method Summary collapse
-
#initialize(source_headers, target_fields) ⇒ AutoMapper
constructor
A new instance of AutoMapper.
- #map ⇒ Object
Constructor Details
#initialize(source_headers, target_fields) ⇒ AutoMapper
Returns a new instance of AutoMapper.
12 13 14 15 16 |
# File 'lib/lcp_ruby/import/auto_mapper.rb', line 12 def initialize(source_headers, target_fields) @source_headers = source_headers @target_fields = target_fields @used_fields = Set.new end |
Class Method Details
.auto_map(source_headers, target_fields) ⇒ Hash
Returns { “Source Column” => “field_name”, … } for matched columns.
8 9 10 |
# File 'lib/lcp_ruby/import/auto_mapper.rb', line 8 def self.auto_map(source_headers, target_fields) new(source_headers, target_fields).map end |
Instance Method Details
#map ⇒ Object
18 19 20 21 22 23 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/lcp_ruby/import/auto_mapper.rb', line 18 def map result = {} # Build reverse lookup indexes by_name = {} by_label = {} # Sentinel value to mark suffix collisions between nested fields. nested_collision = Object.new @target_fields.each do |field| name = field["name"].to_s label = field["label"].to_s by_name[name.downcase] = name by_label[label.downcase] = name unless label.empty? # Nested fields: also index by suffix so "Starting Salary" can match # "hire_detail.starting_salary". Direct fields take priority. next unless name.include?(".") suffix_key = name.split(".", 2).last.downcase if by_name.key?(suffix_key) existing = by_name[suffix_key] # Another nested field owns it → collision, remove both. # Direct field or collision sentinel → leave as is (direct always wins). if existing.is_a?(String) && existing.include?(".") by_name[suffix_key] = nested_collision end else by_name[suffix_key] = name end short_label = label.sub(/^.+→\s*/i, "").strip.downcase unless short_label.empty? if by_label.key?(short_label) existing = by_label[short_label] if existing.is_a?(String) && existing.include?(".") by_label[short_label] = nested_collision end else by_label[short_label] = name end end end # Clean up collision sentinels by_name.reject! { |_k, v| v.equal?(nested_collision) } by_label.reject! { |_k, v| v.equal?(nested_collision) } @source_headers.each do |header| next if header.nil? || header.strip.empty? normalized = header.strip.downcase # Strategy 1: exact field name match (case-insensitive) if by_name[normalized] && !@used_fields.include?(by_name[normalized]) result[header] = by_name[normalized] @used_fields.add(by_name[normalized]) next end # Strategy 2: humanized label match if by_label[normalized] && !@used_fields.include?(by_label[normalized]) result[header] = by_label[normalized] @used_fields.add(by_label[normalized]) next end # Strategy 3: underscore/humanize normalization underscored = normalized.gsub(/\s+/, "_").gsub(/[^a-z0-9_]/, "") if by_name[underscored] && !@used_fields.include?(by_name[underscored]) result[header] = by_name[underscored] @used_fields.add(by_name[underscored]) next end end result end |