Lesson number one when it comes to developing for the web: everything is just data. When you send something to the server in a request, or when you get something back from the server as a response, all you’re really dealing with is data. Simple enough to remember, right? Wrong. Because data can be complicated. Especially when you consider the fact that it has to be passed back and forth in very specific ways. And if you don’t format your data correctly, your computer is going to be very, very mad at you (or probably just throw a really unhelpful error message).
I encountered the complications of data formatting the hard way, while trying to pass data between two parts of my application. I wanted to send some data to update a Ruby object in my Postgres database on the server-side, and then I wanted the Rails side to send back an updated response. As if that wasn’t enough, I then needed the Ember front end to grab the updated data and immediately render it to the user on the client-side. Data formatting can already be complex when you have only one framework or language; throw in another framework and language, and, well…you might feel like you’re having a little bit of a meltdown.
Let’s pretend for a second that we’ve already implemented authentication in our application, which means we have access to the
current_user who is logged in at the highest controller level of our application: the
ApplicationController. In our
UsersController, we want an
index action that will be invoked when the
current_user logs into their account page. On that page, we’ll want to show the user’s “wish lists”, or the list of books that they want to read. Each
WishList object belongs to a
User and has many
Books, and a
Book can belong to a
WishList. Right now in our controller, we are rendering all the
WishLists that are associated with the
current_user as JSON:
1 2 3 4 5 6 7
Cool, we have our
WishLists already rendered as JSON. Why serialize anything in that case? I’m sure our JSON is structured perfectly well, and that we can convert our Ruby object data into Ember models seamlessly!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Oh my. That is definitely not how we want our JSON to look!
For one thing, if we give this JSON to Ember, it won’t know how to turn it into an Ember model. But there are also some other issues with exactly how this data is structured.
For now, we don’t need to render anything to our user about when the created or updated their list, so that’s some superfluous data that we shouldn’t be requesting from the server.
We also don’t want to render anything about our actual
User object – we probably just want to include the
user_id, and nothing else. And because we can load the correct
Book models via Ember itself, let’s not bother with requesting all the details of every
Book in our
WishList. Instead, let’s just get an array of associated
book_ids, and then have Ember render the appropriate ones for us from its data store.
So, to serialize or not to serialize? That is the question. And I think you and I both know the answer.
Before we can get our serializing on, we’ll need to add our new favorite gem to our
and then run the
Now, we’ll want to actually create our serializer, which will work with Rails’ ActiveModel functionality to serialize your persisted Ruby objects into the exact JSON format that we’ll specify. Luckily, we can just generate our serializer instead of creating those files:
This will create a
serializers directory inside of our top-level
/app directory since this is the first serializer we’ve generated. And it’ll add both an empty
application_serializer and a
wish_list_serializer.rb file inside of that new directory, which looks like this:
1 2 3 4 5 6
The attributes that we’ve listed (
name) are the ones that are whitelisted to be serialized. This basically means that these are the attributes we are allowing the
active_model_serializers gem to serialize and make into JSON. The serializer we generated through Rails also recognized the associations that we set up; it created the
belongs_to relationships that we setup inside of our
WishList Rails models.
Cool, but how can we check what our data structure looks like? Well, we can start our server (
rails server) and then head over to where our index route lives (
http://localhost:3000/wish_list). Our JSON response will be rendered through our
index action in our
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Well, our data looks a little bit better. For one thing, our whitelisted attributes and associations have been put under a
wish_lists key, which will have an array of
WishList objects for the
current_user, just as we wrote out in our
Although it’s great that this gem generated all this for us, and for free, we already know that we’re going to have to tweak this a bit. First, let’s get rid of that
belongs_to :user line, and instead just render a
1 2 3 4 5
Because we’ve set up the associations in both models, the
active_model_serializer will look directly for an
id attribute on a
User association, and add that as a key in our JSON object, rather than creating a
user key that points to an entire JSON
User object. What does our JSON object look like now?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Okay, another step in the right direction. But, what about those annoying
Book objects – how do we turn those objects into just an array of
The answer is…through another serializer, of course! And also some snazzy customization.
Customizing Your Serializer
Before we can go about spicing up the JSON response generated by our
WishList Serializer, we are going to have to generate a new serializer for our
Book objects. Thank goodness we already know how to do all that:
which will generate a
book_serializer.rb file like this:
1 2 3 4 5
Step one, successfully accomplished! Now, time to bedazzle our serializer. Let’s say that the front end of our application doesn’t need all that information – instead, it needs just an array of
book_ids. We can just edit our serializer so that we’re only using the information that we want to use:
1 2 3
Now comes step two: telling our
WishList Serializer to refer to our
BookSerializer, and use that to serialize each book associated with a wish list. If only there was an easy way to do that…
1 2 3 4 5 6 7 8
Oh, interesting! We’ve just created our very own attribute on the
WishListSerializer. And what does that method (which is private, because we don’t wany any other part of our application to call it) do, exactly? Well, it creates a new instance of our
BookSerializer, and runs that serializer for each
Book object, returning the attributes that we told it to serializer.
What’s really awesome about this is that if we suddenly decide that we now want not just an array of
ids, but also the
title of each
Book object, we can just add that attribute into our
BookSerializer by modifying the line to
attributes :id, :title, and tada! We have an array of
Book objects that have both an
id and a
title. So easy, right?!
The super cool thing about implementing serializers is that our
UsersController hasn’t changed at all in this process:
1 2 3 4 5 6 7
All that’s happening now is that Rails is looking for a serializer for our
WishList objects on the
current_user, and if it finds one (which it will, since we made it!), it uses that to serialize the appropriate data and render it into a JSON format.
Serializers are pretty fantastic because they’re easy to generate, customize, and use. In fact, we could even create multiple serializer for the same type of object. We could have a
BookDetails Serializer, which might return a ton of information about a book, rather than just its
title. All we’d have to do to use it is specify the serializer within our controller action:
active_model_serializersgem provides a ton of functionality for structuring a JSON response from your Rails API. Read about all the methods it provides in its great documenation.
- Still curious about serializers? Check out this cool post and this one, too!
- Serializers are also great for caching. To learn more about why they matter, read this more advanced blog post!