active_model_serializers
active_model_serializers copied to clipboard
Serializer upgrade question - options (:meta & :fields) in v0.9.x and v0.10.x
Hello AMS team, 👋 🙂
First of all, thank you for maintaining this little gem!
It's not a gem issue, rather an upgrade question and I would really appreciate any feedback.
We've got a Rails 4 app built with AMS v0.9.x and we need to upgrade it to Rails 5.
We're not using many of the advanced AMS features (yet) but rely on 2 features that
had changed since v0.10.x - the meta method and response filtering with :only/:except options.
Following examples and questions assume Attributes adapter is being used in v0.10.x code.
options[:meta]
I'm not sure of the original purpose of :meta but reading v0.10.x docs and stepping through the code, it seems to now be meant specifically for rendering a metadata key in response JSON. Besides that usage we also pass common serialization context to our serializers with it.
For example:
class BillSerializer < ActiveModel::Serializer
attributes :id, :paid_amount, :currency
def currency
# This is the same currency for all bills; could be expensive to fetch per item
meta[:currency].name
end
end
class CustomCollectionSerializer < ActiveModel::Serializer
attributes :data
has_one :pagination, serializer: PaginationSerializer
def data
object.elements.map do |element|
serializer.new(element, {meta: meta}).serializable_hash
end
end
def serializer
@serializer ||= object.elements_serializer
end
end
# controller code
render json: bills,
meta: {currency: common_currency},
each_serializer: BillSerializer,
serializer: CustomCollectionSerializer
produces this JSON:
# API response
{
"data": [
{"id": 1, "paid_amount": 25.0, "currency": "USD"},
{"id": 2, "paid_amount": 37.0, "currency": "USD"},
# etc.
],
"pagination": {
"current_page": 1,
"total_pages": 20,
"per_page": 10,
"total_entries": 199
}
}
I can emulate the old behaviour using a custom key (say, :old_meta) but I wonder if there's a supported option which is more appropriate for passing in serialization context?
options[:fields]
The other problem stems from the same custom collection serializer above - namely, passing :only/:except (white-/blacklist) to a collection item:
class CustomCollectionSerializer < ActiveModel::Serializer
def initialize(resource, options = {})
@element_options = options.slice :only
# We don't want CustomCollectionSerializer's attributes to be filtered ever.
super object, options.except(:only)
end
def data
object.elements.map do |element|
serializer.new(element, {meta: meta}.merge(@element_options)).serializable_hash
end
end
# rest is as above but omitted for brevity
end
In v0.10.x there is only :fields (a whitelist) and it's the adapter's responsibility to apply it. When I just pass it in the controller (with approriate rename of :only key to :fields):
render json: bills,
fields: [:paid_amount],
each_serializer: BillSerializer,
serializer: CustomCollectionSerializer
instead of the correct JSON (i.e. filtered item attributes):
# API response
{
"data": [
- {"id": 1, "paid_amount": 25.0, "currency": "USD"},
+ {"paid_amount": 25.0},
- {"id": 2, "paid_amount": 37.0, "currency": "USD"},
+ {"paid_amount": 37.0},
# etc.
],
"pagination": {
"current_page": 1,
"total_pages": 20,
"per_page": 10,
"total_entries": 199
}
}
I get this (no "data" attribute):
# API response
{
- "data": [
- {"id": 1, "paid_amount": 25.0, "currency": "USD"},
- {"id": 2, "paid_amount": 37.0, "currency": "USD"},
- ],
"pagination": {
"current_page": 1,
"total_pages": 20,
"per_page": 10,
"total_entries": 199
}
}
due to the adapter checking CustomCollectionSerializer#data attribute against the whitelist (fields: [:paid_amount]) and filtering it out. With v0.9.x it was possible to apply the whitelist only to collection item serializer but I can't figure out how to do it with the adapter in v0.10.x.
So, in summary, my questions are:
- What's the next best option to use instead of
:metato pass serialization context to a collection? - How to properly whitelist custom collection item attributes with
:fields?
P.S. I've checked around other issues and found this comment https://github.com/rails-api/active_model_serializers/issues/1864#issuecomment-238826170 but would like to avoid implementing one more never-to-be-supported-upstream feature (like writing a custom adapter class) if possible.
I believe this would be fixed by https://github.com/rails-api/active_model_serializers/pull/2223
Thank you for the response, @bf4! 🙂
The linked PR seems to deal with options[:fields] which is nice (haven't tried it yet); any idea when will it be released?
What about options[:meta] - do you plan to add support for it in :attributes adapter?