27
loading...
This website collects cookies to deliver better user experience
has_many
CEOs, Charities and Cereals. class Company < ApplicationRecord
has_many :ceos
has_many :charities
has_many :cereals
end
class Ceo < ApplicationRecord
belongs_to :company
end
class Charity < ApplicationRecord
belongs_to :company
end
class Cereal < ApplicationRecord
belongs_to :company
end
routes.rb
.Rails.application.routes.draw do
resources :cereals
resources :companies
resources :charities
resources :ceos
end
resources
will automatically include all RESTful routes, which means we can define index
and show
in our cereal controller. (You should typically only include routes that you intend on using, but for now, we'll leave it as is)class CerealsController < ApplicationController
def index
cereals = Cereal.all
render json: cereals
end
def show
cereal = Cereal.find(params[:id])
render json: cereal
end
end
[
{
"id": 1,
"name": "Cheerios",
"company_id": 1,
"price": 3.49,
"ingredients": "*list of ingredients*",
"time_to_make": 1.3,
"transportation_schedule": "*list of schedule details*",
"expiration_date": "Jan 12, 2022",
"warehouse_location": "Cleveland",
"created_at": "2021-12-17T17:55:35.618Z",
"updated_at": "2021-12-17T17:55:35.618Z"
},
{
"id": 2,
"name": "Chex",
"company_id": 1,
"price": 3.99,
"ingredients": "*list of ingredients*",
"time_to_make": 0.9,
"transportation_schedule": "*list of schedule details*",
"expiration_date": "Jan 10, 2022",
"warehouse_location": "Des Moines",
"created_at": "2021-12-17T17:55:35.633Z",
"updated_at": "2021-12-17T17:55:35.633Z"
},
{
"id": 3,
"name": "Lucky Charms",
"company_id": 1,
"price": 4.49,
"ingredients": "*list of ingredients*",
"time_to_make": 1.5,
"transportation_schedule": "*list of schedule details*",
"expiration_date": "Jan 02, 2022",
"warehouse_location": "Minneapolis",
"created_at": "2021-12-17T17:55:35.648Z",
"updated_at": "2021-12-17T17:55:35.648Z"
}
]
bundle add active_model_serializers
rails g serializer cereal
. You will see that this automatically create a serializers folder in your application and add a cereal_serializer to it. I know what you may be thinking, "Generating serializers for all of my models sounds exhausting and time consuming!" And if you do it the way we just did, you'd be right. But, a bonus to the serializer gem is that if you install it at the beginning of your application, before creating any of your migrations, models, or controllers, running rails g resource cereal
will also create a serializer so that you don't have to. By default, it will even include any relationships and attributes that you added to your generation. Since we didn't do that this time around, this is what our serializer looks like right now:class CerealSerializer < ActiveModel::Serializer
attributes :id
end
:id
, the only thing that will be rendered it the cereal id. Taking a look at our GET request again, we see:[
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
]
class CerealSerializer < ActiveModel::Serializer
attributes :name, :company_id, :price, :ingredients,
:expiration_date
end
[
{
"name": "Cheerios",
"company_id": 1,
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022"
},
{
"name": "Chex",
"company_id": 1,
"price": 3.99,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 10, 2022"
},
{
"name": "Lucky Charms",
"company_id": 1,
"price": 4.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 02, 2022"
}
]
show
route.{
"name": "Cheerios",
"company_id": 1,
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022"
}
company_id
that we are getting? That won't be very useful to a buyer. We want to be able to see the actual company name, not just the id. has_many
and belongs_to
, just like in our models. You may notice that if you generate a serializer using resource
, a model that belongs to another will have a serializer with a has_one
association instead of belongs_to
. These operate the same, but I find it easier and more consistent to use belongs_to
. Let's update our cereal serializer with a belongs_to
and see what we get. We also remove the company_id attribute, since we won't be needing that.class CerealSerializer < ActiveModel::Serializer
attributes :name, :company_id, :price, :ingredients, :expiration_date
belongs_to :company
end
{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company": {
"id": 1,
"name": "General Mills",
"employees": 40000,
"healthcare_plan": "*plan description*",
"revenue": 17000000000.0,
"created_at": "2021-12-17T19:05:02.950Z",
"updated_at": "2021-12-17T19:05:02.950Z"
}
}
belongs_to
association. Let's create a serializer for company with rails g serializer company
, and edit the attributes it shows.class CompanySerializer < ActiveModel::Serializer
attributes :name
end
{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company": {
"name": "General Mills"
}
}
[
{
"name": "General Mills"
},
{
"name": "Kellogg's"
},
{
"name": "Quaker Oats"
},
]
belongs_to :company
from our cereal serializer.class CerealSerializer < ActiveModel::Serializer
attributes :name, :price, :ingredients, :expiration_date,
:company_name
def company_name
object.company.name
end
end
object
is a moniker for the object that is being passed to the serializer, similar to self
in class models. Here's what we get on this request:{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company_name": "General Mills"
}
Ceo.first
and Charity.first
in reference to each instance I want to use.class CerealSerializer < ActiveModel::Serializer
attributes :name, :price, :ingredients, :expiration_date,
:company_name, :ceo_name, :ceo_motto, :charity_name,
:charity_cause
def company_name
object.company.name
end
def ceo_name
object.company.ceos.first.name
end
def ceo_motto
object.company.ceos.first.motto
end
def charity_name
object.company.charities.first.name
end
def charity_cause
object.company.charities.first.cause
end
end
{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company_name": "General Mills",
"ceo_name": "Jeff Harmening",
"ceo_motto": "Cereal is our passion.",
"charity_name": "General Mills Foundation",
"charity_cause": "Advancing regenerative agriculture"
}
index
route would return this:[
{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company_name": "General Mills",
"ceo_name": "Jeff Harmening",
"ceo_motto": "Cereal is our passion.",
"charity_name": "General Mills Foundation",
"charity_cause": "Advancing regenerative agriculture"
},
{
"name": "Chex",
"price": 3.99,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 10, 2022",
"company_name": "General Mills",
"ceo_name": "Jeff Harmening",
"ceo_motto": "Cereal is our passion.",
"charity_name": "General Mills Foundation",
"charity_cause": "Advancing regenerative agriculture"
},
{
"name": "Lucky Charms",
"price": 4.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 02, 2022",
"company_name": "General Mills",
"ceo_name": "Jeff Harmening",
"ceo_motto": "Cereal is our passion.",
"charity_name": "General Mills Foundation",
"charity_cause": "Advancing regenerative agriculture"
}
]
...
show
route would be missing all of that data we just worked so hard to get. What we can do is generate a new serializer. Let's call it CustomCerealSerializer
. To do so, we can run rails g serializer custom_cereal
. Now, let's just copy and paste all of our attributes and custom methods into the new serializer:class CustomCerealSerializer < ActiveModel::Serializer
attributes :name, :price, :ingredients, :expiration_date,
:company_name, :ceo_name, :ceo_motto, :charity_name,
:charity_cause
def company_name
object.company.name
end
def ceo_name
object.company.ceos.first.name
end
def ceo_motto
object.company.ceos.first.motto
end
def charity_name
object.company.charities.first.name
end
def charity_cause
object.company.charities.first.cause
end
end
class CerealSerializer < ActiveModel::Serializer
attributes :name
end
index
request will return:[
{
"name": "Cheerios"
},
{
"name": "Chex"
},
{
"name": "Lucky Charms"
}
]
show
request, we will also only recieve the name. What we can do is specify what serializer we want to use in our cereals_controller
.class CerealsController < ApplicationController
def index
cereals = Cereal.all
render json: cereals, status: :ok
end
def show
cereal = Cereal.find(params[:id])
render json: cereal, serializer: CustomCerealSerializer,
status: :ok
end
end
index
route does not have a specified serializer, it will default to the CerealSerializer, which as we just saw, only has the attribute of name. By specifying our custom serializer on the show
route, our return will render those attributes and methods that we defined within it:{
"name": "Cheerios",
"price": 3.49,
"ingredients": "*list of ingredients*",
"expiration_date": "Jan 12, 2022",
"company_name": "General Mills",
"ceo_name": "Jeff Harmening",
"ceo_motto": "Cereal is our passion.",
"charity_name": "General Mills Foundation",
"charity_cause": "Advancing regenerative agriculture"
}
attributes
. This was mainly to show the concept of including only information that would be useful to a user. However, in actual practice, you should typically include the id in your attributes, like so:class SomeSerializer < ActiveModel::Serializer
attributes :id, :name, #etc...
end