ROR: DB relation serializers - ruby-on-rails

I have a model which has a relation:
class buildings
has_many :floors
end
class floors
belong_to :buildings
has_many :rooms
end
class rooms
belong_to :floors
end
and serializers as
class BuildingsSerializer < ActiveModelSerializers::Model
attributes :building_name
has_many :floor
end
class FloorsSerializer < ActiveModelSerializers::Model
attributes :floor_name
belongs_to :building
has_many :room
end
class RoomsSerializer < ActiveModelSerializers::Model
attributes :room_name
belongs_to :floor
end
So when i query as Building.includes(:floor)all the FE is getting
[
{
building_name: "building_one",
floor: [
{
floor_name: "floor_one"
}
]
}
]
the room serializer is not showing. I wanted the returned json which also includes the room detais. The response should include building info, floor info and also room info by queering from Building table, but as of now i am only getting the buinding and floor information. Expected result:
[
{
building_name: "building_one",
floor: [
{
floor_name: "floor_one",
room: [
{
room_name: "room_one"
}
]
}
]
}
]
Thank You

belongs_to associations must use the singular term.
The name of the model should be pluralized when declaring a has_many association.
First correct the typo in associations:
# In Building serializer
has_many :floor to has_many :floors
# In Floor serializer
has_many :room to has_many :rooms
Also frontend query should be as below:
Building.includes(floor: :rooms).all
You can add below changes in serializer and check:
class BuildingSerializer < ActiveModelSerializers::Model
attributes :building_name
attribute :floors
def floors
ActiveModel::Serializer::CollectionSerializer.new(object.floors, serializer: FloorSerializer)
end
end
class FloorSerializer < ActiveModelSerializers::Model
attributes :floor_name
belongs_to :building
attribute :rooms
def rooms
ActiveModel::Serializer::CollectionSerializer.new(object.rooms, serializer: RoomSerializer)
end
end
class RoomSerializer < ActiveModelSerializers::Model
attributes :room_name
belongs_to :floor
end

Related

Rails Api many to many how to send and handle json

I created many to many association into my project and it looks like that:
class A < ApplicationRecord
has_many :C, through: :B
accepts_nested_attributes_for :carray
end
class B < ApplicationRecord
belongs_to :A
belongs_to :C
end
class C < ApplicationRecord
has_many :A, through: :B
end
The extra thing is that I want to save number in every connection between A and C, so B table has additionally column number:integer. Table A and C has name column. My AController looks like that:
class RController < ApplicationController
...
def create
#a = A.new(a_params)
#a.save
end
...
def a_params
params.require(:a).permit([:name, carray_attributes: [:c_id, :number]])
end
end
When I send json:
{
"name" : "A name",
"carray_attributes":
[
{
"id_c": "3",
"number": "23"
},
{
"id_c": "3",
"number": "15"
}
]
}
I get error UnknownAttributeError: unknown attribute 'number' for C. Do you have any idea how to save number into table B?
You need to explicitly create the join model if you need additional attributes besides just the foreign keys. For example:
# rails g model Employee name:string
class Employee < ApplicationRecord
has_many :positions
has_many :companies, through: :positions
accepts_nested_attributes_for :positions
end
# rails g model Position name:string employee:belongs_to company:belongs_to
class Position < ApplicationRecord
belongs_to :employee
belongs_to :company
accepts_nested_attributes_for :company
end
# rails g model Company name:string
class Company < ApplicationRecord
has_many :positions
has_many :employees, through: :positions
end
Here positions is the join table. If we want to create a position with a name attribute while creating a company we need to pass two levels of nested attributes:
Employee.create(
name: 'Bob',
positions_attributes: [
{
name: 'CEO',
company_attributes: {
name: 'Acme Corp.'
}
}
]
)

rails association has many with multiple models

class City < ApplicationRecord
has_many :hospitals
has_many :employees
end
class Hospital < ApplicationRecord
belongs_to :city
has_many :employees
end
class Employee < ApplicationRecord
belongs_to :hospital
belongs_to :city
end
These are my models. Is there any way to get employees for hospital where the hospital located without passing #city parameter in Hospital model.
hospital.rb
has_many :employees, ->(hospital_city) { where(city: hospital_city) }
This works fine. but I need to pass the hospital_city everytime. I want something like this.
has_many :employees, -> { #do something and return employees belongs to hospital & hospital city. }
through not works since employee table has city_id, hospital_id
You could just define a method on Hospital.
class Hospital < ApplicationRecord
belongs_to :city
has_many :employees
def employees_in_city
employees.where(city: city)
end
end

Rails does includes really improve queries ? (with bullet)

# Models
class Cinema < ApplicationRecord
has_many :showtimes
has_many :movies, -> { distinct }, through: :showtimes
end
class Movie < ApplicationRecord
has_many :showtimes
has_many :cinemas, -> { distinct }, through: :showtimes
end
class Showtime < ApplicationRecord
belongs_to :cinema
belongs_to :movie
end
# Controller
class V1::MoviesController < ApplicationController
movie = Movie.find(params[:id])
render json: MovieSerializer.new(movie, include: [..., :cinemas, :'cinemas.showtimes']).serialized_json
end
# Serializers
class MovieSerializer
include FastJsonapi::ObjectSerializer
# Relations
...
has_many :cinemas, serializer: CinemaItemSerializer do |object|
object.cinemas.by_country('FR') #.includes(:showtimes)
end
end
class CinemaItemSerializer
include FastJsonapi::ObjectSerializer
# Relations
has_many :showtimes
end
I use the bullet gem to trigger some n+1 queries and fast_json_api to serialize my objects.
When I don't use includes(:showtimes) I have this response time
If I do use the includes I have this response time
As you see, when I don't use the includes my query is slower but the total response time is way faster.
So which one I should use?

How to return parent data from Rails ActiveModelSerializers?

How can i return parent data from Rails ActiveModelSerializers?
This is my Model
class User < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :groups , through: :user_groups
end
class Group < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :users , through: :user_groups
end
class UserGroup < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
This is my Serializer
class UserSerializer < ActiveModel::Serializer
attributes :id ,:email, :username, :fullname, :grade,:auth_token
has_many :user_groups
end
class GroupSerializer < ActiveModel::Serializer
attributes :id ,:name ,:court_price ,:shuttle_price
has_many :user_groups
end
class UserGroupSerializer < ActiveModel::Serializer
attributes :id , :level
belongs_to :user_id
end
This is my controller
def member_list
group = current_user.groups.find_by(id: params[:id])
respond_with group
end
So i want to return UserGroup data with User data inside but this is what i got.
{
"id": 35,
"name": "test 01",
"court_price": 150,
"shuttle_price": 12,
"user_groups": [
{
"id": 30,
"level": "player"
},
{
"id": 29,
"level": "owner"
}
]
}
How can i return User data inside user_groups array?
Thanks!
You've got to be careful with circular references there. Group embeds UserGroup, that embed User, that also embeds UserGroup.
For that situation, I'd recommend creating a custom serializer for User without the relations. ShallowUserSerializer, for instance.
class ShallowUserSerializer < ActiveModel::Serializer
attributes :id ,:email, :username, :fullname, :grade,:auth_token
end
Aside from that, there's a little problem with UserGroupSerializer. active_model_serializer's docs state:
Serializers are only concerned with multiplicity, and not ownership.
belongs_to ActiveRecord associations can be included using has_one in
your serializer.
So you could rewrite it like this:
class UserGroupSerializer < ActiveModel::Serializer
attributes :id , :level
has_one :user, serializer: ShallowUserSerializer
end

complex query... how to join many classes in rails?

I have the following associations:
class Venue < ActiveRecord::Base
has_many :sales
end
class Sale < ActiveRecord::Base
has_many :sale_lines
has_many :beverages, through: :sale_lines
end
class SaleLine < ActiveRecord::Base
belongs_to :sale
belongs_to :beverage
end
class Beverage < ActiveRecord::Base
has_many :sale_lines
has_many :sales, through: :sale_lines
has_many :recipes
has_many :products, through: :recipes
end
class Recipe < ActiveRecord::Base
belongs_to :beverage
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :recipes
has_many :beverages, through: :recipes
end
I wan't to see the quantity of products sold by each venue, so basically I have to multiply the recipe.quantity by the sale_line.quantity of an specific product.
I would like to call #venue.calc_sales(product) to get the quantity sold of product.
Inside the class Venue I am trying to calculating it by:
class Venue < ActiveRecord::Base
has_many :sales
def calc_sales(product)
sales.joins(:sale_lines, :beverages, :recipes).where('recipes.product_id = ?', product.id).sum('sale_lines.quantity * recipe.quantity')
end
end
However, I can't access the recipes in that way.
Any idea on how to achieve it?
For the joins, you have to use a Hash to join a already-joined table. It's hard to explain, but here are some examples:
Venue.joins(:sales, :beverages) : This implies that the relations :sales and :beverages are declared on the Venue model.
Venue.joins(:sales => :beverages) : This implies that the relation :sales exists on the Venue model, and the relation :beverages exists on the Sale model.
Consider this:
Venue
has_one :sale
Venue.joins(:sales) : This would not work, you have to use the exact same name as the relation between the Venue model & Sale model.
Venue.joins(:sale) : This would work because you used the same name of the relation.
Attention: You have to use the pluralized name in the where clause:
Venue.joins(:sale).where(:sales => { :id => sale.id })
^^ ^^ # See the plural
In your case, you can do something like this:
sales.joins(:sale_lines => { :beverage => :recipes })
.where(:recipes => { :product_id => product.id })
.sum('sale_lines.quantity * recipes.quantity')

Resources