Class: ExportHelper::CSVMappingConverter

Inherits:
Object
  • Object
show all
Defined in:
frontend/app/helpers/export_helper.rb

Overview

Handles header mapping and field transformations for CSV exports

Constant Summary collapse

FIELD_NAME_MAPPINGS =

Map user-requested field names to actual backend field names

{
  'type' => 'type_enum_s',
  'indicator' => 'indicator_u_icusort',
  'barcode' => 'barcode_u_sstr'
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(requested_fields = []) ⇒ CSVMappingConverter

Returns a new instance of CSVMappingConverter.



102
103
104
105
106
107
# File 'frontend/app/helpers/export_helper.rb', line 102

def initialize(requested_fields = [])
  @requested_fields = requested_fields
  # Fetching JSONModel records from the backend creates a lot of overhead, and it's likely that many records have
  # the same ancestor(s), so we'll cache the titles to mitigate that.
  @title_cache = {}
end

Class Method Details

.ancestor_fieldsObject

Cached ancestor fields for performance



93
94
95
# File 'frontend/app/helpers/export_helper.rb', line 93

def self.ancestor_fields
  @ancestor_fields ||= ['ancestors', 'linked_instance_uris', 'linked_record_uris', 'collection_uri_u_sstr', 'digital_object']
end

.header_mappingsObject

Define header mappings from backend field names to user-friendly names Using I18n translations with fallbacks for test environment



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'frontend/app/helpers/export_helper.rb', line 71

def self.header_mappings
  @header_mappings ||= begin
    {
      'context' => I18n.t('reports.headings.context', :default => 'Resource/Accession'),
      'container_profile_display_string_u_sstr' => I18n.t('reports.headings.container_profile', :default => 'Container Profile'),
      'location_display_string_u_sstr' => I18n.t('reports.headings.location', :default => 'Location'),
      'title' => I18n.t('reports.headings.title', :default => 'Title'),
      'type_enum_s' => I18n.t('reports.headings.type', :default => 'Type'),
      'indicator_u_icusort' => I18n.t('reports.headings.indicator', :default => 'Indicator'),
      'barcode_u_sstr' => I18n.t('reports.headings.barcode', :default => 'Barcode')
    }
  end
end

.map_field_name(field) ⇒ Object

Shared method to map a single field name



98
99
100
# File 'frontend/app/helpers/export_helper.rb', line 98

def self.map_field_name(field)
  FIELD_NAME_MAPPINGS[field] || field
end

Instance Method Details

#build_context_from_ancestors(row, headers, ancestor_indices) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'frontend/app/helpers/export_helper.rb', line 205

def build_context_from_ancestors(row, headers, ancestor_indices)
  ancestor_uris = []

  ancestor_indices.each do |index|
    next if index.nil?
    value = row[index]
    next if value.blank?

    if value.include?(',')
      ancestor_uris.concat(value.split(',').map(&:strip))
    else
      ancestor_uris << value.strip
    end
  end

  return nil if ancestor_uris.empty?

  # Build context string from URIs
  context_parts = []
  ancestor_uris.reverse.each do |ancestor_uri|
    next if ancestor_uri.blank?

    title = @title_cache.fetch(ancestor_uri) do |uri|
      begin
        @title_cache[uri] = JSONModel::HTTP.get_json(ancestor_uri)['title']
      rescue
        # If we can't fetch the title, use the URI as fallback
        @title_cache[uri] = uri
      end
    end
    context_parts << title
  end

  context_parts.join(' > ')
end

#build_csv_with_mappings(old_csv) ⇒ Object

Unified CSV building function with dedicated methods for headers and rows



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'frontend/app/helpers/export_helper.rb', line 121

def build_csv_with_mappings(old_csv)
  return old_csv if old_csv.empty?

  # If no specific fields were requested, return the original CSV
  return old_csv if @requested_fields.empty?

  old_headers = old_csv[0]
  # Create the header row
  new_headers = build_header_row(old_headers)
  new_csv = [new_headers]

  # Create data rows
  old_csv[1...old_csv.length].each do |old_row|
    new_row = build_data_row(old_row, old_headers)
    new_csv.append new_row
  end

  new_csv
end

#build_data_row(old_row, old_headers) ⇒ Object

Method 2: Creates a data row with proper field mapping and transformations



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'frontend/app/helpers/export_helper.rb', line 158

def build_data_row(old_row, old_headers)
  row = []

  @requested_fields.each do |requested_field|
    if requested_field == 'context'
      # Build context from ancestor fields
      ancestor_field_indices = self.class.ancestor_fields.map { |field| old_headers.index(field) }.compact
      context_value = build_context_from_ancestors(old_row, old_headers, ancestor_field_indices)
      row << (context_value || '')
    else
      # Map regular fields
      backend_field_name = self.class.map_field_name(requested_field)
      field_index = old_headers.index(backend_field_name)
      value = field_index ? old_row[field_index] : nil
      row << clean_field_value(value)
    end
  end

  row
end

#build_header_row(old_headers) ⇒ Object

Method 1: Creates the header row based on requested fields



142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'frontend/app/helpers/export_helper.rb', line 142

def build_header_row(old_headers)
  headers = []

  @requested_fields.each do |requested_field|
    if requested_field == 'context'
      headers << self.class.header_mappings['context']
    else
      backend_field_name = self.class.map_field_name(requested_field)
      headers << (self.class.header_mappings[backend_field_name] || requested_field.titleize)
    end
  end

  headers
end

#clean_field_value(value) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'frontend/app/helpers/export_helper.rb', line 179

def clean_field_value(value)
  return '' if value.nil?
  return '' if value == 'null'
  return '' if value.is_a?(String) && value.strip.empty?

  # Fix force encoding and remove unnecessary escaping
  # Duplicate the string first to avoid modifying frozen strings
  cleaned_value = if value.is_a?(String)
                    value.dup.force_encoding('utf-8')
                  else
                    value.to_s.force_encoding('utf-8')
                  end

  # Remove backslash escaping of commas since CSV already handles this with quotes
  if cleaned_value.is_a?(String)
    cleaned_value = cleaned_value.gsub('\\,', ',')
    cleaned_value = cleaned_value.gsub("\\'", "'")
    cleaned_value = cleaned_value.gsub('\\"', '"')
    cleaned_value = cleaned_value.gsub('\\n', ' ')
    cleaned_value = cleaned_value.gsub('\\r', ' ')
    cleaned_value = cleaned_value.strip
  end

  cleaned_value || ''
end

#get_backend_field_names(requested_fields) ⇒ Object

Get the actual backend field names for the requested fields



110
111
112
113
114
# File 'frontend/app/helpers/export_helper.rb', line 110

def get_backend_field_names(requested_fields)
  requested_fields.map do |field|
    self.class.map_field_name(field)
  end
end

#map_header_name(header) ⇒ Object



116
117
118
# File 'frontend/app/helpers/export_helper.rb', line 116

def map_header_name(header)
  self.class.header_mappings[header] || header
end