yosiat / panko_serializer

High Performance JSON Serialization for ActiveRecord & Ruby Objects
https://panko.dev
MIT License
592 stars 36 forks source link

Trouble detecting polymorphic relations #65

Open oyeanuj opened 4 years ago

oyeanuj commented 4 years ago

Hi @yosiat, As I was migrating from AMS to this excellent serializer, I keep running into an error where Panko is unable to determine the serializer for a polymorphic relation:

class DrinkSerializer < Panko::Serializer
  has_one :drinkable
  # Since it belongs to either Tea or Coffee, I can't use 'serializer: option'
  # AMS detected it automatically, so how do I tell Panko which one to use? 
end

I couldn't find polymorphic relations mentioned in the docs, so let me know the best way to work with polymorphic relations in Panko.

Thank you!

oyeanuj commented 4 years ago

Ideally, Panko can determine it, or it can provide an option like fast_jsonapi folks provide:

has_many :targets, polymorphic: { Person => :person, Group => :group }
yosiat commented 4 years ago

@oyeanuj when panko handles associations it's simple as calling a method on the subject, for example drink.drinkable so there is no something special there.

If you can provide me with an example with this template - https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb I could find out the root cause and create a PR to fix it.

EDIT:

Now I understand your issue here, the problem is the relation is polymorphic and you don't know which serializer to use?

oyeanuj commented 4 years ago

@yosiat: I think I can clarify - the issue is that the polymorphic relationships aren't being discovered by Panko even though ActiveRecord knows more. So drink.drinkable is leading to a result but Panko throws a runtime error saying there is no serializer for it.

My guess is it is looking for a DrinkableSerializer rather than treating them as polymorphic? If it is hard for Panko to tell, then an option like in Fast_JsonAPI above would be helpful.

oyeanuj commented 4 years ago

@yosiat I wasn't sure how to create a test case for Panko from that template but I did make a template with a polymorphic relation which almost gets us there. Here it is and hope this is helpful:

https://gist.github.com/oyeanuj/b10cc51de54239543d21a66dcf9a4b39

irondnb commented 4 years ago

@oyeanuj Like @yosiat said, you can just calls a method defined in each polymorphic model.

class MembershipSerializer < Panko::Serializer
  attributes :id, :identity, :created_at, :updated_at

  def identity
    object.identity.serialize(
      except: [:updated_at, :created_at]
    )
  end
end
class Membership < ApplicationRecord
  belongs_to :identity, polymorphic: true
end

class Guest < ApplicationRecord
  has_many :memberships, as: :identity, dependent: :destroy

  def serialize(options={})
    GuestSerializer.new(options).serialize(self)
  end
end

class User < ApplicationRecord
  has_many :memberships, as: :identity, dependent: :destroy

  def serialize(options={})
    UserSerializer.new(options).serialize(self)
  end
end

or you can simply create module for reuse.

module Serializable
  def serialize(options={})
    serializer = options[:serializer] || serializer_class_name
    serializer.new(options).serialize(self)
  end

  private

  def serializer_class_name
    "#{self.class.name}Serializer".constantize
  rescue NameError => e
    raise "Missing #{self.class.name} serializer."
  end
end
oyeanuj commented 4 years ago

@irondnb thanks for that helpful example! I think it can certainly be done in the userland but I think it feels weird that it is inconsistent with how other has_one and has_many relations work automagically within a serializer and polymorphic ones don't seem to be. If the inference here works, then they will end up behaving consistently with other relations which would be amazing :)