- Use the
to_json
method to render an object as JSON. - Explain how to render different formats from the same controller action.
Last time out we created a PostSerializer
and used it to serialize a Post
to JSON.
It worked great, but doing all that string concatenation and keeping track of the different quotes was kind of a nightmare. Imagine having to write serializers by hand for objects with more than four fields!
This is something people do every day in Rails, so there has to be a better way, right?
Of course there is. Rails provides the to_json
method which will take our object and, well, convert it to JSON. Let's see it in action. In our controller, let's swap our call to the PostSerializer
for a to_json
.
# posts_controller.rb
# ...
def post_data
post = Post.find(params[:id])
render json: post.to_json
end
Okay, well, surely it can't be that simple. Let's load up our Rails server and browse to /posts
. Click one of the "More" buttons, and, just like that, it updates the post body. We didn't have to change a thing. Think of it as a testament to how great a job we did writing our own serializer.
Now if we click on the first post and use our Next...
link, that should mostly work too.
I say mostly, because it's not updating the author name. That's something we added in to our serializer, but by default, to_json
only serializes the main object, not any associations. How can we change that?
You can tell to_json
what associated objects to include, using the include
option.
# posts_controller.rb
# ...
def post_data
post = Post.find(params[:id])
render json: post.to_json(include: :author)
end
Now if we reload that post show page and click Next
, the author should update as well.
If we browse to /posts/id/post_data
, we can see the raw JSON of our object. It should look something like this:
{
id: 1,
title: "A Blog Post By Stephen King",
description: "This is a blog post by Stephen King. It will probably be a movie soon.",
created_at: "2016-02-22T00:29:21.022Z",
updated_at: "2016-02-22T00:29:21.022Z",
author_id: 1,
author: {
id: 1,
name: "Stephen King",
hometown: null,
created_at: "2016-02-22T00:29:20.999Z",
updated_at: "2016-02-22T00:29:20.999Z"
}
}
Note: This would be a great time to install JSONView if you haven't already!
It's great that to_json
gives us all this, but we don't really need all of it.
A good API endpoint should return only the data that is needed, nothing more. So how do we get rid of that stuff?
It turns out to_json
gives us ways to exclude data as well, using the only
option, similar to how we'd specify certain routes for a resource.
# posts_controller.rb
# ...
def post_data
post = Post.find(params[:id])
#render json: PostSerializer.serialize(post)
render json: post.to_json(only: [:title, :description, :id],
include: [ author: { only: [:name]}])
end
We can use only
both on the main object and the included objects.
Top-tip: Notice that we have to pass author:
inside an array for include
now that we are specifying additional options.
Reloading the /posts/id/post_data
page now gives us something more like this:
{
id: 1,
title: "A Blog Post By Stephen King",
description: "This is a blog post by Stephen King. It will probably be a movie soon.",
author: {
name: "Stephen King"
}
}
Which is exactly the data we need to get the job done.
If we think about what we've been doing when we load /posts/id/post_data
, we're really just requesting a Post
resource, same as if we were on the Post show
page. In fact, that's exactly what we're doing in AJAX on the Post show
page, requesting the data for that page and replacing the values.
Given what we know about REST, and about DRY (don't repeat yourself), it seems like the post_data
route and action are redundant. If we just want to request the post resource for show
, we should be able to do that in one place.
In the desktop application world, we identify formats by file extension, so we know that file.txt
is a plain text file, and file.gif
is an awesome animated gif file.
Rails provides us with a similar way to do this, using respond_to
.
If we go into our show
action and add a respond_to
block, we can specify what to render depending on if the request is looking for HTML or JSON.
# posts_controller
# ...
def show
@post = Post.find(params[:id])
respond_to do |format|
format.html { render :show }
format.json { render json: @post.to_json(only: [:title, :description, :id],
include: [author: { only: [:name]}]) }
end
end
Now if we browse to /posts/id
, we get the HTML page as expected. HTML is the default format for any request. We could also browse to /posts/id.html
, and get the same thing.
But if we browse to /posts/id.json
, we now get our serialized post in JSON form!
Now let's update the code in our show.html.erb
to use the show
route.
# posts/show.html.erb
# ...
$(function () {
$(".js-next").on("click", function() {
var nextId = parseInt($(".js-next").attr("data-id")) + 1;
$.get("/posts/" + nextId + ".json", function(data) {
$(".authorName").text(data["author"]["name"]);
$(".postTitle").text(data["title"]);
$(".postBody").text(data["description"]);
// re-set the id to current on the link
$(".js-next").attr("data-id", data["id"]);
});
});
});
Instead of doing a $.get()
to /posts/id/post_data
, we are now getting /posts/id.json
. If we reload the page and click the Next
button, everything still works and we don't have to change any of the code to extract the JSON values!
We've seen how to use to_json
to easily serialize an object, how to customize the serialized output, and how to modify our actions to respond with different formats.
You're probably thinking about that to_json
call up there and noticing how it went from very simple to a little complex for just a few fields, and worrying about what you'll have to do when you're serializing a big object model? Don't worry. We'll get there.
View Using to_json on Learn.co and start learning to code for free.