I guess it depends on scale as well. The bigger the codebase, the nicer it is to find implementations and references automatically, and the bigger the company, the more likely they are to have good workarounds for the shortcomings of various language ecosystems or even dedicated dev experience teams, in which case Ruby's potential runtime intricacies might really start to weigh it down. Then again, there seems to be some movement away from that all-too-dynamic stuff e.g. in the rails codebase.
That is, yesterday I spent the afternoon trying to follow a maze of signal observers in a Vue3 project. After a moment, not finding why the redirect to login page was nowhere to be found in the signal obsevers of the concerned transition, it finally reveled to be linked to a more broad route hook mechanism that the framework provides.
No meta programming is involved, be it in signal observers or route hook.
Regarding metaprogramming, Crystal keep only the parts that are straightfoward to deal with in static analysis, from what I grasped when skiming its elevator speech.
On Ruby side the great stuffs I like in it are more linked to the homogeneous approach it follows. Almost everything is an object and something like `42.extend(:custom•module).original•action` is possible due to that. It's not 100% pure object though, reserved keywords like `if` can not be used as objects, that is `if.class` is not valid.
Okay, this won't probably be 100% on topic, but in my experience how well these methods work is inversely proportional to the size of the codebase and also quite badly in some cases depending on how the code is written.
The other day I was working on a Java enterprise codebase (monolith, ~300K LoC) and I shouldn't have had that many issues navigating the codebase, however someone made a validator that resolves the logic to a specific class at runtime. So you'd basically have validator.validate(Object someObject). Suddenly if I wanted to find all of the places where validator.validate(MyObject myObject) is called, I could not do that easily and with naming like validator.validate(entity) all over the place, text search didn't help much either. They had an okay type system but chose not to use it, just because they wanted to pass in arbitrary objects, without regard for how easy finding usages will be in the future.
My point is, that you can probably write code that's easy or hard to navigate in any language (within reason), but I'll gladly take whatever tools I can get in any stack out there!
I wonder why no one's written a Ruby LSP with this approach rather than relying on static analysis.
Or is it that the original comment showed no knowledge of programming language history? It’s a tough choice!