Handle API response with value objects
This article is a quick tip for anyone working with APIs in a Rails application and parsing the data without modeling it. Let’s assume that we request the API to get the list of the users from which we would like to pull only active users and save some data for later usage:
response = Request.call('some_url')
parsed_response = JSON.parse(response)
eligible_users = []
parsed_response['people'].each do |person|
if person['active'] == true
eligible_users << {
name: "#{person['firstName']} #{person['lastName']}",
email: person['emailAddress']
}
end
end
eligible_users
The above code is a pretty common approach that you can spot in many legacy applications. If you would like to test this code, you would have to test the request, filtering, and parsing part at once. Also, such code is far from being responsible for one thing.
Usually, when using external APIs, you deal with some resources and data that can be structured similarly to the models in the Rails application. This is the case also right now.
We can say that we have the Person
model, which responds to the following attributes:
- active
- name
We also have code responsible for parsing the response, which we can call as PeopleParser
. Let’s implement the parser first.
The parser for the people
The parser responsibility is to receive a raw response and provide a method that will return an array of the user’s attributes:
class PeopleParser
def initialize(raw_response)
@raw_response = raw_response
end
def people
parsed_json['people']
end
private
def parsed_json
@parsed_json ||= JSON.parse(@raw_response)
end
end
The parsing logic is now extracted (it would make more sense after introducing more refactoring so don’t worry about it now):
response = Request.call('some_url')
parser = PeopleParser.new(response)
eligible_users = []
parser.people.each do |person|
if person['active'] == true
eligible_users << {
name: "#{person['firstName']} #{person['lastName']}",
email: person['emailAddress']
}
end
end
eligible_users
The last step is to build the Person
value object.
Value object for a person
All we need is just a pure ruby object that represents the domain which are the people in our case:
class Person
def initialize(attributes)
@attributes = attributes
end
def active?
@attributes['active']
end
def email
@attributes['emailAddress']
end
def name
"#{@attributes['firstName']} #{@attributes['lastName']}"
end
end
Just a little bit of parsing that is super easy to test. With such a class we can also update our parser:
class PeopleParser
def initialize(raw_response)
@raw_response = raw_response
end
def people
parsed_json['people'].map { |attributes| Person.new(attributes) }
end
private
def parsed_json
@parsed_json ||= JSON.parse(@raw_response)
end
end
We don’t have to worry now about transforming the attributes as parser would provide the necessary object:
response = Request.call('some_url')
parser = PeopleParser.new(response)
eligible_users = []
parser.people.each do |person|
if person.active?
eligible_users << {
name: person.name,
email: person.email
}
end
end
eligible_users
We now have our value objects in action. Guess what? It is not the end of the refactoring. How about duck typing?
Let person behave like a hash
To the array of the eligible users, we need to add a simple hash that consists of the two attributes. How about calling the to_h
method that will transform the Person
object into a hash? Let’s give it a try:
class Person
def initialize(attributes)
@attributes = attributes
end
def to_h
{
name: name,
email: email
}
end
def active?
@attributes['active']
end
def email
@attributes['emailAddress']
end
def name
"#{@attributes['firstName']} #{@attributes['lastName']}"
end
end
The code inside the main loop is less complicated and more intuitive:
response = Request.call('some_url')
parser = PeopleParser.new(response)
eligible_users = []
parser.people.each do |person|
if person.active?
eligible_users << person.to_h
end
end
eligible_users
We could say that we are done, but there is still space for improvements.
Making things more readable
With the Ruby syntax we can make the above code a one liner:
response = Request.call('some_url')
parser = PeopleParser.new(response)
eligible_users = parser.people.select(&:active?).map(&:to_h)
I think we are finally done, nothing left to simplify or make more readable. I am aware that many people dislike such an approach as we transform 12 lines of code into 35 lines, but it’s just a demonstration, an idea I wanted to share.
I hope it would become more reasonable for you with more complex use cases where parsing is not about fetching values from three attributes. Domain design is a powerful tool that makes things more accessible, and value object is a friend of such approach.