Class: Verquest::Transformer

Inherits:
Object
  • Object
show all
Defined in:
lib/verquest/transformer.rb

Overview

Transforms parameters based on path mappings

The Transformer class handles the conversion of parameter structures based on a mapping of source paths to target paths. It supports deep nested structures, array notations, and complex path expressions using dot notation.

Examples:

Basic transformation

mapping = {
  "user.firstName" => "user.first_name",
  "user.lastName" => "user.last_name",
  "addresses[].zip" => "addresses[].postal_code"
}

transformer = Verquest::Transformer.new(mapping: mapping)
result = transformer.call({
  user: {
    firstName: "John",
    lastName: "Doe"
  },
  addresses: [
    { zip: "12345" },
    { zip: "67890" }
  ]
})

# Result will be:
# {
#   user: {
#     first_name: "John",
#     last_name: "Doe"
#   },
#   addresses: [
#     { postal_code: "12345" },
#     { postal_code: "67890" }
#   ]
# }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mapping:) ⇒ Transformer

Creates a new Transformer with the specified mapping

Parameters:

  • mapping (Hash)

    A hash where keys are source paths and values are target paths



43
44
45
46
47
# File 'lib/verquest/transformer.rb', line 43

def initialize(mapping:)
  @mapping = mapping
  @path_cache = {} # Cache for parsed paths to improve performance
  precompile_paths # Prepare cache during initialization
end

Instance Attribute Details

#mappingHash (readonly, private)

Returns The source-to-target path mapping.

Returns:

  • (Hash)

    The source-to-target path mapping



74
75
76
# File 'lib/verquest/transformer.rb', line 74

def mapping
  @mapping
end

#path_cacheObject (readonly, private)

Returns the value of attribute path_cache.



74
# File 'lib/verquest/transformer.rb', line 74

attr_reader :mapping, :path_cache

Instance Method Details

#call(params) ⇒ Hash

Transforms input parameters according to the provided mapping

Parameters:

  • params (Hash)

    The input parameters to transform

Returns:

  • (Hash)

    The transformed parameters with symbol keys



53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/verquest/transformer.rb', line 53

def call(params)
  result = {}

  mapping.each do |source_path, target_path|
    # Extract value using the source path
    value = extract_value(params, parse_path(source_path.to_s))
    next if value.nil?

    # Set the extracted value at the target path
    set_value(result, parse_path(target_path.to_s), value)
  end

  result
end

#extract_value(data, path_parts) ⇒ Object? (private)

Extracts a value from nested data structure using the parsed path parts

Parameters:

  • data (Hash, Array, Object)

    The data to extract value from

  • path_parts (Array<Hash>)

    The parsed path parts

Returns:

  • (Object, nil)

    The extracted value or nil if not found



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/verquest/transformer.rb', line 107

def extract_value(data, path_parts)
  return data if path_parts.empty?

  current_part = path_parts.first
  remaining_path = path_parts[1..]
  key = current_part[:key]

  case data
  when Hash
    # Only check for string keys
    return nil unless data.key?(key.to_s)

    # Always use string keys
    value = data[key.to_s]

    if current_part[:array] && value.is_a?(Array)
      # Process array elements and filter out nil values
      value.map { |item| extract_value(item, remaining_path) }.compact
    else
      # Continue traversing the path
      extract_value(value, remaining_path)
    end
  when Array
    if current_part[:array]
      # Map through array elements with remaining path
      data.map { |item| extract_value(item, remaining_path) }.compact
    else
      # Try to extract from each array element with the full path
      result = data.map { |item| extract_value(item, path_parts) }.compact
      result.empty? ? nil : result
    end
  else
    # For scalar values, return only if we're at the end of the path
    remaining_path.empty? ? data : nil
  end
end

#parse_path(path) ⇒ Array<Hash> (private)

Parses a dot-notation path into structured path parts Uses memoization for performance optimization

Parameters:

  • path (String)

    The dot-notation path (e.g., “user.address.street”)

Returns:

  • (Array<Hash>)

    Array of path parts with :key and :array attributes



92
93
94
95
96
97
98
99
100
# File 'lib/verquest/transformer.rb', line 92

def parse_path(path)
  path_cache[path] ||= path.split(".").map do |part|
    if part.end_with?("[]")
      {key: part[0...-2], array: true}
    else
      {key: part, array: false}
    end
  end
end

#precompile_pathsvoid (private)

This method returns an undefined value.

Precompiles all paths from the mapping to improve performance This is called during initialization to prepare the cache



80
81
82
83
84
85
# File 'lib/verquest/transformer.rb', line 80

def precompile_paths
  mapping.each do |source_path, target_path|
    parse_path(source_path.to_s)
    parse_path(target_path.to_s)
  end
end

#set_value(result, path_parts, value) ⇒ Hash (private)

Sets a value in a result hash at the specified path

Parameters:

  • result (Hash)

    The result hash to modify

  • path_parts (Array<Hash>)

    The parsed path parts

  • value (Object)

    The value to set

Returns:

  • (Hash)

    The modified result hash with symbol keys



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/verquest/transformer.rb', line 150

def set_value(result, path_parts, value)
  return result if path_parts.empty?

  current_part = path_parts.first
  remaining_path = path_parts[1..]
  key = current_part[:key].to_s # Ensure key is a string for consistency

  if remaining_path.empty?
    # End of path, set the value directly
    result[key] = value
  elsif current_part[:array] && value.is_a?(Array)
    # Handle array notation in target path
    result[key] ||= []

    # Process each value in the array
    value.each_with_index do |v, i|
      result[key][i] ||= {}
      set_value(result[key][i], remaining_path, v)
    end
  else
    # Continue building nested structure
    result[key] ||= {}
    set_value(result[key], remaining_path, value)
  end

  result
end