Skip to content

Instantly share code, notes, and snippets.

@ubermajestix
Last active May 22, 2018 21:59
Show Gist options
  • Save ubermajestix/3f25227f4c08014a8bd8 to your computer and use it in GitHub Desktop.
Save ubermajestix/3f25227f4c08014a8bd8 to your computer and use it in GitHub Desktop.
Use Virtus to handle parsing data coming back from an API into easy to work with objects.
# Sick of using Hashes or Hashie::Mash objects when working with API responses?
# Yeah, me too.
# Let's use Virtus to make some nice objects that work like we'd expect.
# For more checkout: https://github.com/solnic/virtus
# Given we get some JSON back from an API like this
{things: [
{name: 'Thing 1', created_at: '2014-09-11 15:57:33 -0700'},
{name: 'Thing 2', created_at: '2014-09-11 15:57:33 -0700'}
]
}
response = RestClient.get('/some/api')
data = JSON.parse(response)
# Usually we'd stop here and do terrible stuff like:
return true if Time.parse(data['things'].first['created_at']) > Time.current
# When working with an API the documentation for the API doesn't live in our
# app, and might not exist at all. When someone new to the project needs to
# change some code that uses an API response, they'll have no idea what the API
# gives them beyond what is stated in your code. They'll have to play around with
# the API from a console, or start asking around costing time and causing
# frustration.
#
# So instead of just using raw responses from an API we can develop an object
# oriented model around the API. There are lots of tools to do this, but using
# Virtus I've found that we side step a lot of issues like parsing the Strings
# a JSON response provides into actual data types in Ruby. This is kind of
# like using ActiveRecord but for APIs, except you define your schema in the
# class file, its actually a lot like DataMapper (an alternate ruby ORM).
#
# When I develop with an API for the first time there will usually be some
# documentation and/or I will be playing around with responses in a console to
# see what the data looks like. Once I have an idea of the kind of data the
# API provides, and the kinds of work I'll need to do with those responses, I
# can quickly setup a Virtus class and define all the attributes of the model
# making it much easier to work with the API responses, it feels like working
# with ActiveRecord objects and not mashing and hashing through terrible data
# structures that end up being stupidily huge to recreate in tests.
class Thing
include Virtus
attribute :name, String
attribute :created_at, DateTime
end
# Wow, that was easy.
# So now we have an object that can parse our API response and we don't have
# to work with undocumented data structures. Wins!
# Having a class like this is also a great place to puts some documentation
# about the Thing you get back from the API and add some helper methods.
# Here's that code again, but using the Virtus class:
response = RestClient.get('/some/api')
data = JSON.parse(response)
things = data['things'].map{|d| Thing.new(d) } # Make all the things into Things!
return true if things.first.created_at > Time.current
# We can make this even better by moving that silly time logic into the class
class Thing
include Virtus
attribute :name, String
attribute :created_at, DateTime
def from_the_future?
created_at > Time.current
end
end
# Even more clean:
response = RestClient.get('/some/api')
data = JSON.parse(response)
thing = Thing.new(data['things'].first) # just get the first one
return thing.from_the_future?
# I hope this shows a better way to work with API responses. This is the tip of
# the iceberg with Virtus its a great way to build a model of an API that is very
# familiar for folks that have been using ActiveRecord.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment