fxn/zeitwerk

Const is missing when a class inherits from another class that refers the first one during its definition

woarewe opened this issue · 3 comments

Hi! I hope you are doing well!
First of all, thank you for such the amazing tool and all the efforts you put into maintaining zero-issue state. That was even a bit uncomfortable to create an issue when there were no others.

So, working on some rails project that based on the graphql gem we noticed an error that happens when rails was trying to autoload a class that inherits from another class and the last one refers to the first one in its body (which means that the second class tries to to get the first class during its initialization). Initially I thought that this was a problem of our project's setup but I tried to reproduce a situation using configuration of bare zeitwerk(without Rails) and got the same error.

Here is repo you can use to see the error. Providing some commands to ease setting it up locally:

git clone https://github.com/woarewe/zeitwerk-circular-dependency-with-inheritance.git
cd zeitwerk-circular-dependency-with-inheritance
bundle
bin/rake # this will fail because of the error

However, if you uncomment this line and call the base class before calling the subclass everything will work just fine.

Versions

Ruby: 3.1.2
Zeitwerk: 2.6.0

File tree

boot.rb - contains zeitwerk configuration
types/base_edge.rb - the subclass
types/base_object.rb - the base class referring the above one

From my understanding, it's circular dependencies and I'm not even sure that it's possible to resolve such the setup somehow.
I tried to check how the default Ruby mechanism(require and require_relative) would work but it didn't work either.

Could you please take a look and say if it's possible to fix somehow? If not, maybe you could suggest some workarounds for that?

P.S. This is not us built such compex setup, this was the code that graphql-ruby generates automatically.

Thank you for your time, have a nice day!

Yeah, that setup wether or not you use zeitwerk in not possible.

If you serialized it it looks like this:

module Types
  superclass = BaseObject # Loading `base_object.rb`, `BaseEdge` isn't yet defined
  class BaseEdge < superclass
  end
end
module Types
  class BaseObject
    BaseEdge # Not loaded yet.
  end
end
fxn commented

Exactly, what @casperisfine said.

Let's consider

# parent.rb
class Parent
  Child
end

# child.rb
class Child < Parent
end

Loading Parent works because by the time you hit Child in parent.rb, the Parent class is already defined (partially), and that is enough for the superclass definition in child.rb to work and succeed, assuming everything else does.

But you cannot load Child directly, because before Ruby is able to create the Child class, it needs its superclass. And in order to define the superclass, you need the subclass. This circularity won't work.

I don't know if that has any practical translation to your GraphQL application, but that is the theory. Does that help? graphql is a popular gem, I guess it normally works?

That was even a bit uncomfortable to create an issue when there were no others.

Haha, appreciate the sentiment, and I'll take that as a compliment. But of course please do open tickets for whatever you may need help with :).

@fxn @casperisfine Thank you guys very much, you have helped a lot.
Regarding the graphql gem. Yes, it normally works but our case was rather a coincidence that discovered such the behavior. Now, I know how to deal with all this.