I work at an enterprise running Rails (you've heard of us, if you're in North America). Discussions about rails abound, and my opinion has distilled into "I love writing Ruby, I loathe reading it"
Same! I had a job at a shop with a monolithic Rails app where I had so much trouble understanding the codebase I almost quit the industry entirely.
Maybe I am just stupid when it comes to reading Ruby/Rails or maybe that codebase was uniquely awful, but it was ~impossible to figure out where things were defined or how data moved through the system. A huge ball of mutable state that was defined at runtime.
When people say "I love writing Ruby" what I hear is "I love writing greenfield Ruby". Everybody loves writing greenfield code! The difference between greenfield and brownfield Ruby is stark, in my experience.
And to be clear I do not hate Ruby. It got me into the industry, it taught me a lot, it just optimizes for a set of values that I don't happen to share anymore, which is fine.
A lot of Ruby's syntax lends itself towards cramming as much business logic on the screen as it can. I used to say it's a "semantically dense" language, but I don't actually know if that's technically accurate. Compared to Java or Rust I certainly felt like Ruby fit more "logic" into a screenful of code, but at the cost of any other context (type annotations, import notes, etc)
I strongly appreciate how much decision fatigue Rails avoids by just offering you so much batteries-included stuff. I tried getting into Django and immediately spun out fretting over what ORM or migration manager or caching system to use. (One of my coworkers who is a huge Django says I'm nuts and that Django offers those things too, so I may be misremembering.) Rails being as opinionated as it is saves so much thinking effort along those lines.
I think both of those facets make it extremely appealing to, as you say, anyone greenfielding code, and are the exact things that make it an absolute trash fire to maintain anything of appreciable size.
We're constantly fielding incidents due to something being undefined or unexpected nils or other basic typecheck failures.
> it just optimizes for a set of values that I don't happen to share anymore
That is a lovely way to put it, I'm gonna steal that
But awful to navigate - the terse syntax combined with lack of static types and regular use of generated identifiers turns large codebases into Where's Wally. Good luck finding where the `process` function is called from. You can't even search for `process(` like you can in most languages.
Well, just add "puts caller" in the function to find out. You can do this in your own code, but also you can also just briefly patch the library you're working with, if that's where the process is.
By the way, the generated identifiers are more a rails thing than a ruby thing.
Doesn't that just tell you the functions that happen to call it when you run a program? That's not remotely as good as just getting a complete list at the click of a button.
It's the job of the code editor to get that information surfaced to me at the proper place (e.g virtual content like vim inlay hints, hover tooltips, those vscode embeds or whatever they are called). If I need to jump quickly I have projections† set up.
Having the types inline isn't that useful anyway as it gives yo only the signature but gives you no info of whatever type intermediate things are without much thinking.
And interesting things happen when you have separate `.rbs` files: I started to develop some things type first then progressively fill the implementation in.