Inexplicable superclass mismatch

This is in a Rails project, though this is more a generic Ruby question.

I have a "superclass mismatch" error in my project that I can't explain.
Probably a class is being loaded twice, which is wrong but still should
not cause an error. Can anyone explain it?

Api::V3::PostDecorator.superclass

=> Api::V3::BaseDecorator

class Api::V3::PostDecorator < Api::V3::BaseDecorator; end

!! #<TypeError: superclass mismatch for class PostDecorator>

How is that a mismatch? The superclass is exactly what I am declaring as
I reopen the class.

···

--
Posted via http://www.ruby-forum.com/\.

The fact that they have the same name does not mean they are the same
object. A minimal example goes like this:

    class C
    end

    class D < C
    end

    Object.send(:remove_const, :C)

    class C
    end

    D.superclass # => "C"
    C # => "C"

    D.superclass.object_id # => 70208043796780
    C.object_id # => 70208043796660

For some reason the classes in your code are different objects.

FWIW I tend to prefer class_eval for reopening because I don't like to
repeat the superclass. Specifying the superclass belongs to the class
definition in my mind. But that is just a comment in passing, you need to
figure out why are the object_ids different. I'd suspect some constant not
being reloaded.

Indeed:

Api::V3::PostDecorator.superclass

=> Api::V3::BaseDecorator

Api::V3::PostDecorator.superclass.object_id

=> 112209320

Api::V3::BaseDecorator.object_id

=> 131824380

So the only way this can happen is if the constant is deleted and
recreated? In my tests, modifying the class doesn't change its ID.

···

--
Posted via http://www.ruby-forum.com/\.

I know which code causes the problem - it's some manual loading that
should probably not be there. But you've helped me understand why the
error occurs so thanks a lot.

···

--
Posted via http://www.ruby-forum.com/.

Indeed:

>> Api::V3::PostDecorator.superclass
=> Api::V3::BaseDecorator

>> Api::V3::PostDecorator.superclass.object_id
=> 112209320

>> Api::V3::BaseDecorator.object_id
=> 131824380

So the only way this can happen is if the constant is deleted and
recreated?

There are other scenarios like using an expression as superclass like a
Struct constructor for example, but with the code you showed that's the
only reasonable assumption I can think of. A remove_const somewhere.

The way constant reloading works in Rails is that autoloaded constants are
removed with remove_const, so when a new request arrives constant
autoloading is triggered on demand again. But note this is all about
constants, if you did X = C somewhere, and C was remove_const'ed, X would
still hold the class object. It is an important distinction,
dependencies.rb technically deals with constants, it unloads and loads
constants, not classes or modules (though they are in practice as a
side-effect and works if you follow the golden path).

I'd need to know more about your application to be able to help further.
For example, which are the files where these constants are defined and
reopened?

···

On Thu, Aug 29, 2013 at 11:33 AM, Leslie Viljoen <lists@ruby-forum.com>wrote:

#remove_const is not needed - overwriting is sufficient

irb(main):001:0> class A;end
=> nil
irb(main):002:0> class B < A; end
=> nil
irb(main):003:0> B.superclass
=> A
irb(main):004:0> B.superclass.equal? A
=> true
irb(main):005:0> A = Class.new
(irb):5: warning: already initialized constant A
=> A
irb(main):006:0> B.superclass.equal? A
=> false
irb(main):007:0> B.superclass
=> A
irb(main):008:0> A
=> A

Kind regards

robert

···

On Thu, Aug 29, 2013 at 12:20 PM, Leslie Viljoen <lists@ruby-forum.com> wrote:

I know which code causes the problem - it's some manual loading that
should probably not be there. But you've helped me understand why the
error occurs so thanks a lot.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Robert Klemme wrote in post #1119939:

···

On Thu, Aug 29, 2013 at 12:20 PM, Leslie Viljoen <lists@ruby-forum.com> > wrote:

I know which code causes the problem - it's some manual loading that
should probably not be there. But you've helped me understand why the
error occurs so thanks a lot.

#remove_const is not needed - overwriting is sufficient

irb(main):001:0> class A;end
=> nil
irb(main):002:0> class B < A; end
=> nil
irb(main):003:0> B.superclass
=> A
irb(main):004:0> B.superclass.equal? A
=> true
irb(main):005:0> A = Class.new
(irb):5: warning: already initialized constant A
=> A
irb(main):006:0> B.superclass.equal? A
=> false
irb(main):007:0> B.superclass
=> A
irb(main):008:0> A
=> A

Yes but "Class.new" will always produce a class with a new object_id -
that's not the same as reopening the class, which leaves the object_id
intact.

--
Posted via http://www.ruby-forum.com/\.

Robert Klemme wrote in post #1119939:

I know which code causes the problem - it's some manual loading that
should probably not be there. But you've helped me understand why the
error occurs so thanks a lot.

#remove_const is not needed - overwriting is sufficient

irb(main):001:0> class A;end
=> nil
irb(main):002:0> class B < A; end
=> nil
irb(main):003:0> B.superclass
=> A
irb(main):004:0> B.superclass.equal? A
=> true
irb(main):005:0> A = Class.new
(irb):5: warning: already initialized constant A
=> A
irb(main):006:0> B.superclass.equal? A
=> false
irb(main):007:0> B.superclass
=> A
irb(main):008:0> A
=> A

Yes but "Class.new" will always produce a class with a new object_id -
that's not the same as reopening the class, which leaves the object_id
intact.

Yes, obviously. But what is your point? With reopening the class you
cannot produce the error you showed initially, can you?

irb(main):001:0> class A;end
=> nil
irb(main):002:0> class B < A; end
=> nil
irb(main):003:0> class B < A; def x; end end
=> nil
irb(main):004:0> class B; def y; end end
=> nil
irb(main):005:0> class A; def z; end end
=> nil
irb(main):006:0> class B < A; def a; end end
=> nil
irb(main):007:0> class B; def b; end end
=> nil
irb(main):008:0> A = Class.new
(irb):8: warning: already initialized constant A
=> A
irb(main):009:0> class B; def c; end end
=> nil
irb(main):010:0> class B < A; def e; end end
TypeError: superclass mismatch for class B
        from (irb):10
        from /usr/bin/irb:12:in `<main>'

You said earlier:

So the only way this can happen is if the constant is deleted and
recreated? In my tests, modifying the class doesn't change its ID.

And to that I replied that deleting the constant (i.e. #remove_const)
is not necessary (see quote at the beginning).

Kind regards

robert

···

On Thu, Aug 29, 2013 at 11:08 PM, Leslie Viljoen <lists@ruby-forum.com> wrote:

On Thu, Aug 29, 2013 at 12:20 PM, Leslie Viljoen <lists@ruby-forum.com> >> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/