In Controllers, Less is More (Rails APIs)

Zoe Kirsh
6 min readJan 25, 2021

An API, aka Application Programming Interface, is essentially a database of information that can be tapped into and used by external systems. By external systems, I mean a program anyone can write. There are many free APIs available that host a wealth of different topics, but you can also use Rails to create your own API from scratch.

This is a great tool for beginners, because you can populate your Rails API with literally any kind of data you want and interact with that data to create a little app of moderate substance. Sure, if you’re a mega expert (or super nerd) about something, you can fill your Rails API with knowledge from your actual brain, but if you just want to practice coding skills I love the Faker gem for this. The possibilities are endless! And often silly, which is always a plus.

One massive pro of using Rails to create an API is that Rails APIs render JSON strings. This format is perfectly digestible for those building their frontend with javascript (🙋🏽‍♀️ like moi). Another huge pro is that as the creator of the API, you have complete control over how your information is structured. Since the purpose of an API is to be an accessible interface, you always want to be thoughtful in how you structure data and how that data will be utilized. Well structured API data makes frontend code simpler. And thus too, your life.

The controller is the walkie-talkie between frontend and backend. The bridge. The connective tissue. There’s a direct correlation between data defined in the controller and the data your frontend has access to. Be intentional and specific when defining the json rendered data in your controller actions, and you will have a much cleaner pool of data that your frontend fetches. Let’s look at a simple example.

Anytime you use $rails g resource to create your models and migrations, Rails automatically adds attributes of [:created_at] and [:updated_at]. When our frontend is parsing the json data, we want to keep things as streamlined as possible and only use the information we need. The bigger your app gets, the more important this becomes. Scale, people. But even on a minimal level like in this example, we simply have no use for the two attributes mentioned above, and therefore do not want to include it when we fetch the data in our js file. In our controller file, we can be explicitly selective about which attributes are included in the object we’ll render to json using two simple tools: except and only.

Use except when you want to include most information, and exclude only a few certain attributes (like :created_at and :updated_at). Use only in opposite situations, when there are only a few attributes you do want to include.

An example of using except

As you can imagine, this scales. Say you have a User class model with 10 attributes. Some of these pieces of information you want to keep private and do not want to pass through. We can use except or only to protect information like passwords, phone numbers, email addresses, etc.

An example of using only

What about being inclusive? Another crucial design factor to set yourself up for success is the way you convey relationships between multiple models. If we declare the relationship in the model and set things up correctly in the controller (first image), we can simply access the relational data in our javascript file like so (second image):

(A movie has many characters)
In the JavaScript file

By telling the controller to include: [:model] we render the json object with an additional key whose value is a nested array of hashes. What a thing of beauty. By presenting the data already as a part of the json object, we avoid complicated enumerables and unnecessary struggle.

Great, but what if we want to be selective about the information included from each of the associated models? This is where things can get hairy. With one additional relational model (character), you can have something like this:

Now imagine you have five relational models. Or more. Your controller will quickly get clogged up with a tangle of curly braces, hash rockets, probably a missing comma somewhere, and become an absolute nightmare. Not to mention very un-dry (moist? yuck) when you need to have the same code in more than one action. Enter: Service Class.

Who’s a good developer? YOU.

A service class is a class specific to our domain that handles some of the business logic. In this case, we are handling the logic of customizing our json data the way we desire. We don’t have to necessarily change our working code, just transfer the heavy lifting off the controller, to somewhere more appropriate. Whatever they say about clean rooms and desk spaces is definitely also true of controllers. But what if there was a gem to do the cleaning for you?

gem 'fast_jsonapi'

Gives you access to the serializer generator, which can be run from the command line. Once you create your serializers, all there is left to do is tell it which attributes you’d like to include. You can add related objects by simply declaring the relationships in the serializer class (just like you do in the model) using belongs_to or has_many. Then, back in the controller action, create a hash with one key/value pair:

You can include multiple associated models in the array, separated by commas

On the next line in our render json statement, we will simply pass the hash as a second argument in the signature of our serializer invocation. Fast JSON API automatically serializes the attributes. The only downside to using this gem is that the outcome of the data is more deeply nested, under :type and :attributes, etc. and we lose the ability to explicitly design the structure of our json data.

If you prefer to take the time to explicitly design the structure of your json data, you can follow the forthcoming steps.

Make a new folder called services within app. Create a file in your new folder that will define a new class. Name the class your model name plus Serializer (ex: MovieSerializer). In this class we can create two methods, initialize and another method that will contain the logic. We can make use of Ruby’s instance variables so that the variable can be shared across both methods. The second method is where the magic really happens. Instead of having a dense illegible block of code, we can abstract the data into a hash. We give the hash two keys: include and except. The value of include should also be a hash, defining your relational models and the attributes you wish to include (using only/except). The value of except should be the same as before. Finally, so that the function returns the correct data, we call .to_json on the instance variable from initialize and pass it the hash we just made. Observe:

The options hash should look familiar!

Now we can go back to the controller and replace the old code with the following:

This separation of concerns leaves our code modular and readable, our controller DRY and rid of clutter, and free to do what it is really only meant to do: act as the switch board between our models and our output to the frontend. Whether you choose to use the gem or manually create your serializer class, the biggest takeaway should be to make use of either of these tools to keep your controller nice and clean and tidy.

--

--