Leveraging Ruby's Select in a ActiveRecord Serializer To Get Desired Nested Array

craigjford

Craig Ford

Posted on April 5, 2023

Leveraging Ruby's Select in a ActiveRecord Serializer To Get Desired Nested Array

Suppose that you want a nested array of data that fits a certain criteria rather than all the rows returned in a has_many and has_many through association. How would one go about implementing a solution? One way would be to use the Select method.

As an example, suppose you have a car application that has a user, car and dealer association in the following diagram. You would like to get a nested array of a user, its dealers and ONLY cars for the specific user associated with a dealer instead of every user that has an association with the dealer.

users -< cars >- dealers
Enter fullscreen mode Exit fullscreen mode

Following are the user, car and dealer models to further clarify the associations.

class User < ApplicationRecord

    has_many :cars
    has_many :dealers, through: :cars

end
Enter fullscreen mode Exit fullscreen mode
class Car < ApplicationRecord

  belongs_to :user
  belongs_to :dealer

end
Enter fullscreen mode Exit fullscreen mode
class Dealer < ApplicationRecord

    has_many :cars
    has_many :users, through: :cars

end
Enter fullscreen mode Exit fullscreen mode

If we make a show call to the User and a User Serializer with just User columns, you will simply get a User hash with User data. But, we want a return nested array of a user, its dealers and ONLY the user's cars.

Let's make the following change to our User serializer.

class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :first_name, :last_name, 
                         :dealers_with_cars 

  def dealers_with_cars
      byebug  
  end

end
Enter fullscreen mode Exit fullscreen mode

We will now add a method "dealers_with_cars" to our show serializer as shown above. When we now run our show query, we will be stopped at the byebug. At the byebug, query "self.object.dealers". This query will retrieve all the dealers associated with the user.

We have found a way to get our first nested array of the User's dealers. Now we want to get another nested array of cars that will hold only cars that belong to specified user rather that all of the dealer's cars.

If we now map through our dealers (or "self.object.dealers"), we can now get access to each dealer's cars. If we now change our User serializer to the following we get every user's cars associated with this dealer.

class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :first_name, :last_name, 
                     :dealers_with_cars 

  def dealers_with_cars
     self.object.dealers.map do |dealer|
        dlr = {}
        dlr[:id] = dealer.id
        dlr[:name] = dealer.name
        dlr[:contact] = dealer.contact
        dlr[:phone] = dealer.phone
        dlr[:email] = dealer.email
        dlr[:cars] = dealer.cars
        dlr
     end  
  end

end
Enter fullscreen mode Exit fullscreen mode

If we now run our User show query, we get the following results. As you can see, it includes cars that do NOT belong to the specific user which is not what we want as you can see below.

{
"id": 5,
"username": "craig",
"first_name": "Craig",
"last_name": "Ford",
"dealers_with_cars": [
    {
       "id": 13,
       "name": "Baja Motors",
       "contact": "Bryan Knotts",
       "phone": "6095288443",
       "email": "b_knotts@baja.com",
       "cars": [
          {
                 "user_id": 5,
                 "dealer_id": 13,
                 "year": 1989,
                 "make": "Datsun",
                 "model": "210",
          },
          {
                 "user_id": 6,
                 "dealer_id": 13,
                 "year": 2006,
                 "make": "Ford",
                 "model": "Escort",
          },
          {
                 "user_id": 21,
                 "dealer_id": 13,
                 "year": 2020,
                 "make": "Nissan",
                 "model": "Spirit",
          }
       ]
    },
    {
       "id": 19,
       "name": "Stagger Inn",
       "contact": "John Moos",
       "phone": "8127864433",
       "email": "jmoos@stagger.com",
       "cars": [
          {
                 "user_id": 5,
                 "dealer_id": 19,
                 "year": 2010,
                 "make": "Mercedes",
                 "model": "GL 450",
          },
          {
                 "user_id": 11,
                 "dealer_id": 19,
                 "year": 2016,
                 "make": "Ford",
                 "model": "Escort",
          }
       ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Fortunately, we only need to add the Array Select method to our Cars array to get our desired result. If we change the User to the following, we will get the desired results.

class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :first_name, :last_name, :dealers_with_cars 

  def dealers_with_cars
     self.object.dealers.map do |dealer|
        dlr = {}
        dlr[:id] = dealer.id
        dlr[:name] = dealer.name
        dlr[:contact] = dealer.contact
        dlr[:phone] = dealer.phone
        dlr[:email] = dealer.email
        dlr[:cars] = dealer.cars.select {|car| car.user_id == 
                                               self.object.id}
        dlr
     end  
  end

end
Enter fullscreen mode Exit fullscreen mode

We now get our desired results.

{
"id": 5,
"username": "craig",
"first_name": "Craig",
"last_name": "Ford",
"dealers_with_cars": [
    {
       "id": 13,
       "name": "Baja Motors",
       "contact": "Bryan Knotts",
       "phone": "6095288443",
       "email": "b_knotts@baja.com",
       "cars": [
          {
                 "user_id": 5,
                 "dealer_id": 13,
                 "year": 1989,
                 "make": "Datsun",
                 "model": "210",
          },
          {
                 "user_id": 6,
                 "dealer_id": 13,
                 "year": 2006,
                 "make": "Ford",
                 "model": "Escort",
          }
       ]
    },
    {
       "id": 19,
       "name": "Stagger Inn",
       "contact": "John Moos",
       "phone": "8127864433",
       "email": "jmoos@stagger.com",
       "cars": [
          {
                 "user_id": 5,
                 "dealer_id": 19,
                 "year": 2010,
                 "make": "Mercedes",
                 "model": "GL 450",
          }
       ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading!

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
craigjford
Craig Ford

Posted on April 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About