11 July 2020
Welcome to the fourth chapter of the API-only rails course. In this part, we will integrate the activemodel serializers
to our project. If you didn’t read the previous chapters of the course, you can check all content from the course page.
In the previous chapter, we have created the first version of the user creation endpoint. If we send a new request to create a user, the API response looks as follows.
By default, ruby on rails adds all columns of the model to the API response when we render a resource with JSON format. But let’s imagine we don’t want to expose updated_at
value from the API endpoint for the created user. The simplest way of doing it without using any library is overriding the as_json
method in the model, which is used by rails internally while serializing ruby objects. Let’s modify our User
model to see it in action.
Inside of the as_json
method, we defined the fields we want to see in the response payload and called super method to get standard behavior from the ActiveRecord::Base
with specified fields. (We also merged with options parameter to keep other options like status code.) If we send another request to create a new user now:
We will get an API response without an updated_at
field.
But as you can imagine, we would like to manipulate API payloads that will have much more complexity than the previous example. We will use nested associations with different data structures, and it will become hard to maintain with overriding the as_json
method.
To solve the problem we mentioned in a structured and straightforward way, we can use other libraries to manipulate the JSON structure of the API responses. We have different options like fast_jsonapi and jbuilder for serializing ruby objects. However, I would like to use active_model_serializers gem because it’s mature enough to have all functionality needed for serializers. But instead of using the latest version of the activemodel serializer (0.10.10)
, I will use version 0.8.4
because of performance reasons.
Let’s add active_model_serializers
to the Gemfile
and run bundle install
command.
Then let’s create app/serializers/user_serializer.rb
file and add id
, email
, created_at
, and updated_at
fields to the serializer.
So if we send this request with curl
, we will create a new user and see the user’s payload as we specified in the user serializer. (Don’t forget to remove the as_json
method we defined at the beginning of the chapter for demonstration purposes.)
With the new serializer, the API response should be as following.
But how activemodel serializer knows which serializer class to use? It basically looks for the resource class name and searches for the serializer with the same name. For our use case, it looks for UserSerializer
class. We also have an option to pass serializer name explicitly like render json: @user, serializer: UserSerializer
in the controllers.
As you can see, activemodel serializers automatically adds the serializer name as a root key. Still, keep in mind that when we introduce the renderer
module in the next chapters, we will refactor the root key part, and our API responses will have a general abstraction with two root fields as data
and meta
as follows.
We completed the activemodel serializer integration, and it’s working as expected. But I realized that I would like to use id
, created_at
, and updated_at
fields for all of my serializers. To do that, I can create a BaseSerializer
(similar to BaseController
for API controllers) and extend other serializers from the base serializer.
So we can refactor UserSerializer
with removing id
, created_at
and updated_at
fields and extending it from BaseSerializer
.
We successfully completed the integration of base serializer and used the user serializer inherited from base serializer.
We can complete our serializer implementation with writing tests for both base and user serializers. Let’s start with building spec/factories/users.rb
file since we will use the user factory to test serializers. The user model has presence validation for both email
and password
fields so that we can add those fields to the factory file.
We filled the password with the static value, but we used sequence for the email because we want to differentiate email addresses since they have to be unique, and we might create more than one user in the same spec. You can have more information about sequences from FactoryBot’s github page.
Now we can start writing the base serializer test.
We have created a resource (we used User
because we only have the user model at the moment) and checked whether its id
, created_at
, and updated_at
values matched with the serialized resource.
We can also use the same approach while testing user serializer.
We also need to refactor creating a new user test case inside of spec/requests/api/v1/users_spec.rb
file since active model serializers added the user
as a root key.
And now we can run bundle exec rspec
command to see all tests are green.
Lastly, I want to mention that we can use create
and build
methods directly instead of using them as FactoryBot.create(...)
and FactoryBot.build(...)
. To do that, we need to modify spec/rails_helper.rb
file as following.
And now, let’s remove the FactoryBot
from create
and build
methods.
Now, we will run the rspec
command again to make sure everything works as they were before.
In this chapter, we integrate the activemodel serializer into the project. We also created the base and user serializers with corresponding spec files. You can find the source code of bookmarker application on github. In the next chapter, we will introduce the renderer
module for API controllers. You can also find the previous chapter here.
Thanks for reading! I would love to hear your feedback about the chapter and the course in general. You can contact me by email or from twitter.