This example demonstrates how to use Moxml for XML API interactions, including building SOAP requests, parsing responses, and handling namespaces.
- XML Request Building: Creating SOAP requests with Moxml::Builder
- Response Parsing: Extracting data from XML API responses
- Namespace Handling: Working with multiple XML namespaces (SOAP, custom)
- Authentication: Including authentication headers
- Error Handling: Robust error handling for API interactions
- Data Structuring: Converting XML responses to Ruby objects
api_client.rb- Main API client implementationexample_response.xml- Sample SOAP API responseREADME.md- This file
ruby examples/api_client/api_client.rbruby examples/api_client/api_client.rb path/to/your/response.xmlSOAP API Client Example
================================================================================
Building SOAP Requests
================================================================================
1. GetUser Request:
--------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" ...>
<soap:Header>
<AuthHeader xmlns="http://api.example.com/auth">
<SessionId>session-abc-123</SessionId>
<Timestamp>2024-10-30T10:00:00Z</Timestamp>
<RequestId>req-a1b2c3</RequestId>
</AuthHeader>
</soap:Header>
<soap:Body>
<GetUserRequest xmlns="http://api.example.com/users">
<UserId>1001</UserId>
</GetUserRequest>
</soap:Body>
</soap:Envelope>
[Additional request examples...]
Parsing SOAP Response
================================================================================
Response Information:
--------------------------------------------------------------------------------
Status: 200 - Success
Session ID: a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6
Request ID: req-12345
Metadata: {:response_time=>"45", :server_version=>"2.1.0", :cache_hit=>"false"}
User Data:
--------------------------------------------------------------------------------
User ID: 1001
Username: johndoe
Email: john.doe@example.com
Full Name: John Doe
Role: Administrator
Status: Active
Created: 2024-01-15T08:30:00Z
Last Login: 2024-10-29T14:22:00Z
Permissions: users.read, users.write, admin.access
Profile:
Department: Engineering
Title: Senior Developer
Location: San Francisco, CA
PhoneNumber: +1-555-0123
Result: SUCCESS ✓
Use Moxml::Builder to create well-formed SOAP envelopes:
doc = Moxml::Builder.new(@moxml).build do
declaration version: "1.0", encoding: "UTF-8"
element 'soap:Envelope',
'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
element 'soap:Header' do
# Authentication headers
end
element 'soap:Body' do
# Request payload
end
end
endDefine namespaces as constants for reusability:
NAMESPACES = {
'soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
'auth' => 'http://api.example.com/auth',
'users' => 'http://api.example.com/users'
}.freeze
# Use in XPath queries
doc.at_xpath('//users:User', NAMESPACES)Extract data using namespace-aware XPath:
# Extract with namespace
user_id = extract_text(doc, '//users:User/users:Id', NAMESPACES)
# Extract arrays
permissions = doc.xpath('//users:Permission', NAMESPACES).map(&:text)
# Extract nested structures
profile = doc.at_xpath('//users:Profile', NAMESPACES)
department = extract_text(profile, './users:Department', NAMESPACES)Handle parsing and query errors:
begin
doc = @moxml.parse(xml_string)
response = parse_response(doc)
rescue Moxml::ParseError => e
puts "Parse error: #{e.message}"
rescue Moxml::XPathError => e
puts "Query error: #{e.message}"
endInclude authentication in SOAP headers:
element 'soap:Header' do
element 'AuthHeader', 'xmlns' => NAMESPACES['auth'] do
element 'SessionId' do
text session_id
end
element 'Timestamp' do
text Time.now.utc.iso8601
end
end
endRepresents API user data:
- Basic info (id, username, email, etc.)
- Permissions array
- Profile hash
Encapsulates API response:
- Status code and message
- Response data
- Metadata (response time, version, etc.)
- Session and request IDs
Main API client with methods:
build_get_user_request- Build GetUser SOAP requestbuild_create_user_request- Build CreateUser SOAP requestparse_response- Parse SOAP responseparse_user- Extract user dataextract_metadata- Extract response metadata
<soap:Envelope>
<soap:Header>
<AuthHeader>
<!-- Authentication data -->
</AuthHeader>
</soap:Header>
<soap:Body>
<OperationRequest>
<!-- Request parameters -->
</OperationRequest>
</soap:Body>
</soap:Envelope><soap:Envelope>
<soap:Header>
<AuthHeader>
<!-- Session info -->
</AuthHeader>
</soap:Header>
<soap:Body>
<OperationResponse>
<Status>
<!-- Status code/message -->
</Status>
<Result>
<!-- Response data -->
</Result>
<Metadata>
<!-- Additional info -->
</Metadata>
</OperationResponse>
</soap:Body>
</soap:Envelope>Create new request builders:
def build_delete_user_request(user_id)
Moxml::Builder.new(@moxml).build do
declaration version: "1.0", encoding: "UTF-8"
element 'soap:Envelope', 'xmlns:soap' => NAMESPACES['soap'] do
element 'soap:Header' do
# Auth header
end
element 'soap:Body' do
element 'DeleteUserRequest', 'xmlns' => NAMESPACES['users'] do
element 'UserId' do
text user_id.to_s
end
end
end
end
end
endParse different response types:
def parse_list_response(doc)
users = doc.xpath('//users:User', NAMESPACES)
users.map { |user_elem| parse_user(user_elem) }
endHandle SOAP faults:
fault = doc.at_xpath('//soap:Fault', NAMESPACES)
if fault
fault_code = extract_text(fault, './faultcode')
fault_string = extract_text(fault, './faultstring')
raise "SOAP Fault: #{fault_code} - #{fault_string}"
end- Define once: Keep namespace URIs in constants
- Use prefixes: Consistent prefixes make code readable
- Pass to XPath: Always include namespaces in queries
- Document: Comment namespace purposes
# Good: Clear namespace management
NAMESPACES = {
'soap' => 'http://schemas.xmlsoap.org/soap/envelope/', # SOAP envelope
'auth' => 'http://api.example.com/auth', # Authentication
'users' => 'http://api.example.com/users' # User operations
}.freeze
# Use consistently
doc.xpath('//users:User', NAMESPACES)Include elements conditionally:
element 'soap:Body' do
element 'Request' do
element 'UserId' do
text user_id
end
# Optional filter
if filter
element 'Filter' do
text filter
end
end
end
endCreate multiple elements:
permissions.each do |perm|
element 'Permission' do
text perm
end
endBuild complex hierarchies:
element 'User' do
element 'BasicInfo' do
element 'Username' do
text username
end
end
element 'Profile' do
element 'Department' do
text department
end
end
end- Builder pattern: Clean, readable XML construction
- Namespaces: Critical for SOAP and enterprise XML
- XPath + namespaces: Powerful data extraction
- Structure data: Convert XML to Ruby objects
- Error handling: Robust API client behavior
- Reusability: Extract common patterns to methods
Test API clients thoroughly:
# Test request building
request = client.build_get_user_request(123)
assert request.to_xml.include?('<UserId>123</UserId>')
# Test response parsing
response = client.parse_response(sample_xml)
assert_equal 200, response.status_code
assert response.success?To use with actual APIs:
- Replace
example_response.xmlwith real responses - Add HTTP client (e.g., Net::HTTP, HTTParty)
- Handle network errors
- Implement retry logic
- Add request/response logging
- Implement authentication refresh
- Add more API operations (update, delete, list)
- Implement HTTP transport layer
- Add request/response logging
- Implement authentication token refresh
- Handle pagination in list responses
- Add response caching
- Implement request rate limiting
- RSS Parser - XPath and namespace handling
- Web Scraper - DOM navigation techniques