rails-api / active_model_serializers

ActiveModel::Serializer implementation and Rails hooks
MIT License
5.32k stars 1.39k forks source link

each_serializer throws NoMethodError (demodulize) for instances of anonymous classes #2435

Closed makmic closed 2 years ago

makmic commented 2 years ago

Expected behavior vs actual behavior

The serializer should be able to render collection of records that have an anonymous superclass without an exception:

user = Class.new {}.new
render json: user, each_serializer: UserSerializer

The actual behavior is an exeption: NoMethodError (undefined method demodulize for nil:NilClass). It results from an serializer name lookup using the superclass chain - which should be unnecessary as I'm explicitely stating the each_serializer.

A workaround is to specify the serializer twice:

user = Class.new {}.new
render json: user, each_serializer: UserSerializer, serializer: UserSerializer

While the example above might seem abstract, there are relevant real world use cases. For example, active type form models inject an anonymous class to the inheritance chain.

Steps to reproduce

The most minimal example to reproduce the issue would be this:

require 'active_model_serializers'
class UserSerializer  < ActiveModel::Serializer; end
user = Class.new {}.new
UserSerializer.new(user).class.serializer_lookup_chain_for(user.class)

This is triggers the same exception as for the render statement above - see Backtrace below.

Environment

ActiveModelSerializers Version: 0.10.13 Output of ruby -e "puts RUBY_DESCRIPTION": ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-linux] OS Type & Version:

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.4 LTS"

Integrated application and version (e.g., Rails, Grape, etc): Rails 6.0

Backtrace

        7: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model/serializer.rb:67:in `serializer_lookup_chain_for'
        6: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model/serializer.rb:67:in `flat_map'
        5: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model/serializer.rb:67:in `each'
        4: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model/serializer.rb:68:in `block in serializer_lookup_chain_for'
        3: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model_serializers/lookup_chain.rb:52:in `block in <module:LookupChain>'
        2: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model_serializers/lookup_chain.rb:78:in `serializer_from'
        1: from ~/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/active_model_serializers-0.10.13/lib/active_model_serializers/lookup_chain.rb:70:in `resource_class_name'
NoMethodError (undefined method `demodulize' for nil:NilClass)

Additonal helpful information

/

bf4 commented 2 years ago

you write

user = Class.new {}.new
render json: user, each_serializer: UserSerializer

but your example is not rendering a collection. Is that a bug in the example?

I wonder because the below is not correct usage of serializers vs collection serializers

 each_serializer: UserSerializer, serializer: UserSerializer

If you're going to serialize anonymous things as users, maybe specify the root key? (been a while since I've been in the code, but I'm pretty sure you can specify this)

makmic commented 2 years ago

You are right, I used each_serializer by mistake in this case. Using only the serializer argument fixes the problem. I'll close this issue as I cannot reproduce it with the corrected arguments.

Regarding the root key: I'm using one, it was just omitted here :)

Thanks for helping me out!