
July 2, 2026
Modern applications rarely consume external data exactly as it arrives. Whether you’re integrating with payment gateways, CRMs, ERPs, or third-party APIs, incoming payloads almost always need to be normalized, enriched, or reshaped before they can be processed.
A common workflow looks like this:
Incoming Payload │ ▼Transformation Layer │ ▼Normalized Payload │ ▼Business Logic
Keeping this transformation layer isolated makes applications easier to maintain, test, and extend as new integrations are added.
The Transformation Layer
Instead of scattering mapping logic across controllers, services, or models, encapsulate it behind a dedicated transformer.
class CustomerTransformer def self.call(payload) { full_name: "#{payload[:first_name]} #{payload[:last_name]}", email: payload[:email].downcase.strip, active: payload[:status] == "ACTIVE" } endend
Usage is straightforward:
normalized = CustomerTransformer.call(payload)
This approach is simple, testable, and works well for most Rails applications.
Strategy Pattern for Multiple Integrations
When every provider sends a different payload, each integration can have its own transformer.
Incoming Payload │ ▼Transformer Factory │ ├── ShopifyTransformer ├── StripeTransformer ├── SalesforceTransformer └── SAPTransformer
Example:
class TransformerFactory def self.build(source) { stripe: StripeTransformer, shopify: ShopifyTransformer, salesforce: SalesforceTransformer }.fetch(source) endendtransformer = TransformerFactory.build(:stripe)normalized = transformer.call(payload)
Adding a new integration becomes a matter of creating a new transformer instead of modifying existing code.
Configuration Instead of Code
Sometimes the transformation consists only of field mapping.
Instead of writing Ruby code, store the mapping configuration.
customer_name: first_namecustomer_email: emailcustomer_phone: phone
Then apply it dynamically:
mapping.each_with_object({}) do |(target, source), result| result[target] = payloadend
This lets non-developers update mappings without requiring a deployment.
DSLs for Business Rules
When transformations become more expressive but still follow predictable patterns, a DSL can provide flexibility without exposing arbitrary code execution.
For example:
full_name: concat: - first_name - " " - last_nameactive: equals: field: status value: ACTIVE
The application interprets the configuration and produces the final payload.
This approach keeps behavior configurable while maintaining full control over what operations are allowed.
JSONata
If most payloads are JSON documents, JSONata is an excellent alternative.
A JSONata expression like:
{ "full_name": first_name & " " & last_name, "email": $lowercase(email)}
can transform complex JSON structures with very little code.
It’s especially useful when integrations change frequently or payloads contain deeply nested objects.
Executing Custom Code Safely
Some platforms allow customers to define custom transformation logic.
Instead of executing user code inside the application process, modern systems isolate execution using dedicated processes, containers, or sandboxed runtimes.
A typical architecture looks like this:
Payload │ ▼Isolated Execution Environment │ ▼Validated Result │ ▼Application
This prevents custom logic from affecting the stability or security of the main application.
Testing Transformers
Transformation code is deterministic, making it ideal for unit tests.
RSpec.describe CustomerTransformer do it "normalizes the payload" do payload = { first_name: "John", last_name: "Doe", email: " JOHN@EXAMPLE.COM " } expect(described_class.call(payload)).to eq( full_name: "John Doe", email: "john@example.com", active: false ) endend
Fast, isolated tests help ensure every integration behaves as expected.
Choosing the Right Approach
There is no single solution for every project.
- Use dedicated Ruby transformer classes for most applications.
- Use the Strategy Pattern when supporting multiple providers.
- Use configuration files when transformations are mostly field mappings.
- Use a DSL for configurable business rules.
- Consider JSONata for JSON-heavy integrations.
- Isolate execution if custom user code is required.
By treating payload transformation as a first-class architectural concern, Ruby and Ruby on Rails applications become easier to evolve, safer to extend, and significantly more maintainable as the number of integrations grows.
