Building an API with Rails is super easy. Active Model makes managing your domain model relationships and querying your database headache-free. Namespacing your API routes only requires a few lines of code and mapping your controllers to namespaced routes only requires one line of code. Active Model Serializers make your JSON responses extremely customizable. On top of all that Rails generators will whip up the structure of your build with a single bash command. Oh ya, and they even have a flag to indicate that you’re generating an API.
rails new my_api --api
Yes, it’s that easy. Rails leaves a lot to ‘the man behind the curtain’ but it also gives developers easy-to-use tools to expand on whatever they drum up with generators.
Let’s work up an example domain: we want to keep track of users that visit our business.
- A Business has_many Visits
- A User also has_many Visits
- Visits belong_to both Businesses and Users
- Visits act as a join table between Users and Businesses
- Businesses/Users has_many of each other through Visits.
We’re concerned with finding Visits that overlap with each other. So let’s put columns in the Visits table for :time_in and :time_out. Let’s also add a boolean :flagged column to the table and have it default to false (this will come in handy later). I won’t dive into how to set these resources and relationships up but you can read more about that here: Rails Generators, Rails Associations.
How do we find overlapping visits you ask? Well let’s think about the conditions of an overlap:
- userA and userB need to visit the same Business (duh)
- AND userA doesn’t leave before userB arrives
- AND userB doesn’t leave before userA arrives
If all of those conditions are true then the visits in question will have some overlap time with each other. We can reflect these conditions in an instance method inside of the Visit model:
Now that we’ve got that in our back pocket let’s talk about Active Model Serializers. First off: to use them you need to include
in your GemFile then
bundle install. We want to use them so that our JSON responses have
overlap_visits nested in them. We’ll run
rails g serializer visit to generate the serializer, then fill it with a few attributes:
The beauty of this serializer is that if you call
render json: visit in any controller action the JSON will automatically be formatted according to the attributes specified in the serializer. The response will have the
:time_out attributes inside of the JSON object as well as all top-level attributes of
:user. Pretty sweet. Now let’s get fancy and add
:overlap_visits to the serializer. We define an instance method inside the serializer that calls the .
overlap_visits instance method we defined in the Visits model. Before we do that, let’s take a look at an instance of VisitSerializer:
Ok — main takeaway: if we want to interact with the instance of Visit while we’re inside of the VisitSerializer we will call
self. Now let’s move on to that VisitSerializer instance method — inside of it we’ll map over
object.overlap_visits (because its an array of Visits), and serialize each Visit. Don’t forget to add
:overlaps to the attributes list:
Ok now lets seed some overlapping visits and send a get request to /visits/1 to test things out…
Oh no! We’re making an infinitely nested JSON object. No worries. That’s why we gave Visit a :flagged attribute! We’re gonna do 2 things: temporarily flag a visit in our controller action, and we’ll make
:overlaps a conditional attribute in our serializer like so:
And BOOM! You’ve got custom conditional nested attributes in your JSON object. One thing worth noting is that we aren’t saving the Visit after we set
:flagged to true — this is so it goes back to being nil after the response is sent (for future requests). This is only scratching the surface with Rails and serialization.