In Active Model Serializers.. Let's say I have CompanySerializer
and EmployeeSerializer
. On CompanySerializer
I have a field ceo
where I want to render the association with EmployeeSerializer
but only show a subset of the fields from EmployeeSerializer
.
For the purposes of this example, let's say that EmployeeSerializer
has the attributes name
, homepage
, and salary
, but on CompanySerializer.ceo
we only want to show name
and homepage
.
I would like to avoid redefining a new CompanyEmployeeSerializer
class, if I can just "pick" the fields that I want for the employee association inside CompanySerializer
.
I also want to avoid a solution that runs EmployeeSerializer
then slices for specific fields. The reason for this is I want to avoid running the code associated with serializing the non-desired fields.
Note: this is different than the question of how you hide certain fields inside a serializer based on a condition. This is about how you can call a serializer from another serializer and get specific fields.
If something like this existed, I'd be quite happy:
CompanySerializer < ActiveModel::Serializer
belongs_to :ceo, serializer: EmployeeSerializer, fields: [:name, :homepage]
end
This is as close as I could get:
# app/serializers/company_serializer.rb
class CompanySerializer < ActiveModel::Serializer
belongs_to :ceo do
EmployeeSerializer.new(object.ceo).attributes([:name, :homepage])
end
end
Could you do it this way?
# app/serializers/company_serializer.rb
class CompanySerializer < ActiveModel::Serializer
belongs_to :ceo, serializer: EmployeeSerializer, fields: [:name]
end
You'd have to dig deep:
$ git diff
diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb
index 5e34779..e50f44f 100644
--- a/lib/active_model/serializer.rb
+++ b/lib/active_model/serializer.rb
@@ -367,7 +367,9 @@ module ActiveModel
adapter_options ||= {}
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
if (fieldset = adapter_options[:fieldset])
- options[:fields] = fieldset.fields_for(json_key)
+ if fields = fieldset.fields_for(json_key)
+ options[:fields] = fields
+ end
end
resource = attributes_hash(adapter_options, options, adapter_instance)
relationships = associations_hash(adapter_options, options, adapter_instance)
diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb
index 7aeee33..ce0cd29 100644
--- a/lib/active_model/serializer/association.rb
+++ b/lib/active_model/serializer/association.rb
@@ -55,7 +55,8 @@ module ActiveModel
association_object = association_serializer && association_serializer.object
return unless association_object
- serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
+ options = {fields: reflection.options[:fields]}.compact
+ serialization = association_serializer.serializable_hash(adapter_options, options, adapter_instance)
if polymorphic? && serialization
polymorphic_type = association_object.class.name.underscore
Monkey patch to give it a try:
# config/initializers/serializer_patch.rb
ActiveModel::Serializer::Association.class_eval do
def serializable_hash(adapter_options, adapter_instance)
association_serializer = lazy_association.serializer
return virtual_value if virtual_value
association_object = association_serializer && association_serializer.object
return unless association_object
# pass `fields:` option from the reflection to serializer
options = {fields: reflection.options[:fields]}.compact
serialization = association_serializer.serializable_hash(adapter_options, options, adapter_instance)
if polymorphic? && serialization
polymorphic_type = association_object.class.name.underscore
serialization = {type: polymorphic_type, polymorphic_type.to_sym => serialization}
end
serialization
end
end
ActiveModel::Serializer.class_eval do
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
adapter_options ||= {}
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
if (fieldset = adapter_options[:fieldset])
# fix controller `fields:` always overriding our patched fields option
# even if you don't pass `fields:` to `render`:
if fields = fieldset.fields_for(json_key)
options[:fields] = fields
end
end
resource = attributes_hash(adapter_options, options, adapter_instance)
relationships = associations_hash(adapter_options, options, adapter_instance)
resource.merge(relationships)
end
end
Test:
>> CompaniesController.renderer.render(json: Company.first)
=> "{\"id\":1,\"name\":null,\"ceo\":{\"name\":null}}"