Last active
May 22, 2018 21:59
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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