Presenting a hypothetical, simplified version of my problem. Imagine I have a website where users can create a Shop with their own branding and choose from a catalog of Products to show in their Shop. Shops & Products have a has-and-belongs-to-many (HABTM) relationship. Each Product has its own Shop-specific route.
Rails.application.routes.draw do
resources :shops do
resources :products
end
end
class ShopSerializer < ActiveModel::Serializer
has_many :products
end
class ProductSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :url do
shop_product_url(NEED SHOP ID, product_id: object.id)
end
end
When a Shop is serialized, and as a result, so is the collection of its Products, I want the Product serializer to be aware of the shop that is serializing it and use that to include the route in the serialized output. How is this possible? I've tried all manner of passing instance_options
from the ShopSerializer
but it doesn't work as expected.
# this works except is apparently not threadsafe as multiple
# concurrent requests lead to the wrong shop_id being used
# in some of the serialized data
has_many :products do
ActiveModelSerializers::SerializableResource.new(shop_id: object.id).serializable_hash
end
# shop_id isn't actually available in instance_options
has_many :products do
ProductSerializer.new(shop_id: object.id)
end
Unfortunately serializer associations do not seem to provide a clean way to pass in custom attributes to the child serializers. There are a few not-so-pretty solutions though.
1. Invoke ProductSerializer
manually, add URL in ShopSerializer
class ProductSerializer < ActiveModel::Serializer
end
class ShopSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :products do
object.products.map do |product|
ProductSerializer.new(product).serializable_hash.merge(
url: shop_product_url(object.id, product_id: product.id)
)
end
end
end
2. Add shop ID to the Product
instances before they are fed to ProductSerializer
class ProductSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :url do
shop_product_url(object.shop_id, product_id: object.id)
end
end
class ShopSerializer < ActiveModel::Serializer
has_many :products, serializer: ProductSerializer do
shop = object
shop.products.map do |product|
product.dup.tap do |instance|
instance.singleton_class.send :define_method, :shop_id do
shop.id
end
end
end
end
end
Both solutions should be thread safe, but the first solution seems like a better idea to me, as the second one makes ProductSerializer
unusable on its own — i.e. when just a single Product
is serialized without knowing the particular shop it should belong to.