Leveraging Ruby's Select in a ActiveRecord Serializer To Get Desired Nested Array
Craig Ford
Posted on April 5, 2023
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
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
class Car < ApplicationRecord
belongs_to :user
belongs_to :dealer
end
class Dealer < ApplicationRecord
has_many :cars
has_many :users, through: :cars
end
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
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
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",
}
]
}
]
}
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
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",
}
]
}
]
}
Thanks for reading!
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
November 30, 2024