The Power of Serialization in Active Record
Tags: rails, webdev, tutorial, database
Published: 2023-05-15 14:12:20

The nice thing about using Rails is that it allows us to use Active Record to create a database of organized information.  With client-server communication the user can request data and have it returned and displayed in the browser.  The user triggers an event (hitting a like button, submitting a form, and on and on).  Information is requested and/or sent using a fetch request.  This fetch request at the minimum includes an HTTP verb such as GET, POST, PATCH, and DELETE and a URL which includes a domain name and a path.  If information is sent it is typically included in a configuration object which contains a method (the aforementioned HTTP verbs except for GET which is implicit when you are only fetching data), headers which is typically {"Content-Type": "application/json"} to send JSON data, and a body which contains data which is converted to JSON (JSON.stringify(consult)).  Once the promise is fulfilled then JSON data is returned.

I will delve into how this JSON data is returned.  Basically I will talk about the power of serialization.  Ever since I discovered serializers, I feel powerful.

![I got the power](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3460rvbphavji719t9zz.gif)

But in all seriousness using serializers is a great way to customize the way that data is displayed on the backend.  I will briefly talk about how this is achieved and show examples from my own projects.  Let us start by defining serialization.  Data serialization is the process of converting/translating data into a format that can be shared (over a network) or stored (in a file, in memory or in a database) and then be converted back to its original structure.  

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0jj79veustxie3v42p6.png)

For our purposes, serialization is taking our JSON output and formatting it in a desired fashion to facilitate the use of this data on the frontend (client).

First we need to setup our backend to allow us to use serializers.  Serialization can be done in our controllers but it can get complicated and cumbersome very quickly, especially if the models contain a lot of attributes.  Serializers allow separation of concerns which means that while the controllers communicate with our models to render data, serializers determine how that data is displayed.  ActiveModel::Serializer (or AMS) provides an easy way to customize how the JSON rendered by our controllers is structured.

Since Ruby is about gems, guess what?  We will install another gem.  Let us install the gem for our serializer in our Gemfile.

```
# Gemfile
#...
gem 'active_model_serializers'
```
Now run `bundle install` in your console. Next generate your serializer for your model by running the command `rails g serializer your-model-name`.  Let's assume our model name is 'model'.  The serializer that was just generated will look like this.  Here, we can list that attributes that we want displayed.

```
class ModelSerializer < ActiveModel::Serializer
  attributes :id, :attribute1, :attribute2, ...
end
```
For many if not most cases this would be all you need.  Think about it, you can decide which attributes you want displayed and which ones you want excluded.  However, what if you just need more.  With the use of our associations (has_many, belongs_to) and customized instance methods we can tap into the full power of serialization.  It's kind of like being one with the force (if you are a fan of Star Wars) or tapping into the speed force (if you like the Flash).

Watch as I proceed to illustrate this with examples from my own projects.  I will start with serializing associations.  Let's begin with my `Provider` model.

**The schema**

```
  create_table "providers", force: :cascade do |t|
    t.string "username"
    t.string "password_digest"
    t.string "first_name"
    t.string "middle_name"
    t.string "last_name"
    t.string "type_of_provider"
    t.string "department"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
```
**The model**

```
class Provider < ApplicationRecord
    validates :username, :first_name, :last_name, :type_of_provider, :department, presence: true
    validates :username, uniqueness: true

    has_secure_password
    has_many :appointments
    has_many :patients, through: :appointments
end
```
**Serializer**

```
class ProviderSerializer < ActiveModel::Serializer
  attributes :id, :username, :first_name, :middle_name, :last_name, :type_of_provider, :department
  has_many :appointments
  has_many :patients
end
```

**JSON Output**

```
  {
    "id": 3,
    "username": "doctorstrange",
    "first_name": "Stephen",
    "middle_name": "Allen",
    "last_name": "Strange",
    "type_of_provider": "Physician",
    "department": "Neurosurgery",
    "patients": [
      {
        "id": 7,
        "first_name": "Cletus",
        "middle_name": "Ziemann",
        "last_name": "Altenwerth",
        "birth_date": "1961-02-19T00:00:00.000Z",
        "sex": "M",
        "image": "",
        "address": "778 Vance Summit Jewellton, Arkansas 05528-0096",
        "phone_number": "1-486-568-3938 x766",
        "email_address": "roberto@wolf.test",
        "insurance": "Aetna",
        "age": 62,
        "picture": null
      },
      {
        "id": 9,
        "first_name": "Kayce",
        "middle_name": "MacGyver",
        "last_name": "DuBuque",
        "birth_date": "1988-06-30T00:00:00.000Z",
        "sex": "F",
        "image": "",
        "address": "910 Ankunding Road East Jacquelynborough, Kentucky 95561",
        "phone_number": "(346) 455-8971 x90000",
        "email_address": "glendora@greenfelder.test",
        "insurance": "Cigna",
        "age": 34,
        "picture": null
      }
    ],
    "appointments": [
      {
        "id": 18,
        "provider_id": 3,
        "patient_id": 7,
        "type_of_appointment": "Inpatient: Surgery",
        "location": "NYU Langone Brain and Spine Center",
        "date": "2023-05-06T19:30:00.674Z"
      },
      {
        "id": 19,
        "provider_id": 3,
        "patient_id": 9,
        "type_of_appointment": "Outpatient: Consultation",
        "location": "NYU Brain and Spine Center",
        "date": "2023-05-06T19:12:48.112Z"
      }
    ]
  }
```
In the above example, I took a model called `Provider` and used a serializer called `ProviderSerializer` to customize the JSON output to display the attributes of `Provider` as listed in the attribute section of the serializer as well as all patients and appointments for Dr. Strange using the known associations for this model.

Let's take note of a few things.  Note that `ProviderSerializer` inherits from `ActiveModel::Serializer`.  Furthermore the name of the serializer follows the typical Rails 'convention over configuration' in that the name is the model name (Singular!) and Serializer.  Note that all attributes except the `password digest`, `created_at`, and `updated_at` were included for display.  The use of the `has_many` macro allowed the display of all patients and appointments associated with Dr. Strange.

Let's kick this up a notch.  Instead of returning all information on the patients and the appointments for this provider return an list of patients and appointment with just the basic information.  We can use custom instance methods in our serializer to do this.

**Serializer**
```
class ProviderSerializer < ActiveModel::Serializer
  attributes :id, :username, :first_name, :middle_name, :last_name, :type_of_provider, :department, :patient_list, :appointment_list
  
  def patient_list
    patients=self.object.patients
    patients.map do |patient|
      "#{patient.last_name}, #{patient.first_name} DOB: #{patient.birth_date.strftime("%m/%d/%Y")}"      
    end
  end

  def appointment_list
    appointments=self.object.appointments
    appointments.map do |appointment|
      patient=Patient.find(appointment.patient_id)
      "#{patient.last_name}, #{patient.first_name} || Type of appointment: #{appointment.type_of_appointment} || Location: #{appointment.location } || Date: #{appointment.date.strftime("%m/%d/%Y")}" 
    end
  end
end
```
Note that I created two instance methods patient_list and appointment_list to return lists of the patients and appointments respectively in abbreviated form.  Also note the use of `self.object` to refer to `self` (instance of `Provider`) is similar to the way you would use `self` in Ruby.  Here is our JSON output below.  Note how much nicer this looks, so sweet and so smooth.

**JSON Output**
```
  {
    "id": 3,
    "username": "doctorstrange",
    "first_name": "Stephen",
    "middle_name": "Allen",
    "last_name": "Strange",
    "type_of_provider": "Physician",
    "department": "Neurosurgery",
    "patient_list": [
      "Altenwerth, Cletus DOB: 02/19/1961",
      "DuBuque, Kayce DOB: 06/30/1988"
    ],
    "appointment_list": [
      "Altenwerth, Cletus || Type of appointment: Inpatient: Surgery || Location: NYU Langone Brain and Spine Center || Date: 05/06/2023",
      "DuBuque, Kayce || Type of appointment: Outpatient: Consultation || Location: NYU Brain and Spine Center || Date: 05/06/2023"
    ]
  }
```
As one can see serializers are another tool in our Rails toolbox that we can use to enhance the richness of our applications.  In this example a streamlined patient list and appointment list was generated for Dr. Strange entirely from the backend with no work on the front end.  This can be done with any resource provided that certain conditions are met.  Serialization can be taken to the next level using associations and custom methods.  This is the power of Serialization.  I'm out.