group_by with ActiveModel::Serializers

Posted . Visible to the public.

When using group_by in a controller that serializes the grouped collection to JSON you will notice that your serializers created with the active_model_serializer will not be used and all attributes in your model are just converted to JSON. For example:

class SomeModel 
  #attributes: :name, :body, :some_group_key
end

class SomeModelSerializer 
  attributes :name
end

# in controller
def index
  @some_models = SomeModel.all.group_by(&:some_group_key)
  render json: @some_models, each_serializer: SomeModelSerializer
end

So this will result in something like this:

{ 
  "GROUP_KEY_VALUE" => [
    { "name" => ..., "body" => ..., "some_group_key" => "GROUP_KEY_VALUE", "created_at" => ... ...},
    { "name" => ..., "body" => ..., "some_group_key" => "GROUP_KEY_VALUE", "created_at" => ... ...}
  ],
  "GROUP_KEY_VALUE2" => [
    { "name" => ..., "body" => ..., "some_group_key" => "GROUP_KEY_VALUE2", "created_at" => ... ...},
    ...
  ]
}

So even when an explicit serializer is given, it will be ignored since the serializer will find a hash instead of the collection and all attributes will be rendered out.

To change this, so that the serializer will be used, write a custom serializer:

class GroupedSomeModelSerializer < ActiveModel::Serializer
  
  # method override
  def serializable_hash
    @object.map do |some_group_key, some_models|
      [ some_group_key , serialized_some_models(some_models) ]
    end.to_h
  end

  private

    def serialized_some_models some_models
      some_models.map{ |some_model| SomeModelSerializer.new(some_model, root: false) }
    end

end

And then in the controller you just need call your serializer as an each serializer:

# in controller
def index
  @some_models = SomeModel.all.group_by(&:some_group_key)
  render json: @some_models, serializer: GroupedSomeModelSerializer
end

Then the result should be grouped and use your serializer:

{
  "GROUP_KEY_VALUE" => [
    { "name" => ...},
    { "name" => ...}
  ],
  "GROUP_KEY_VALUE2" => [
    { "name" => ...},
     ...
  ]
}

active_model_serializers > 0.9

For the new versions of AMS you need to override serializeable_object instead of serializable_hash:

  def serializable_object(options={})
    @object.map do |some_group_key, some_models|
      [ some_group_key , serialized_some_models(some_models) ]
    end.to_h
  end
  

Find this on stackoverflow Show archive.org snapshot

Andreas Knöpfle
Last edit
Andreas Knöpfle
Posted by Andreas Knöpfle to BitCrowd (2016-02-29 14:13)