Hacker News new | ask | show | jobs
by sensanaty 96 days ago
I love Rails, but after working for a few places with huge Rails codebases and then several other places with .NET and other frameworks with actual typing, I just can't go back to Rails for anything that isn't a personal project.

Working with a large codebase with an untyped codebase is just a nightmare, even with powerful IDEs like RubyMine that are able to cover some of the paint points. I wonder how good Sorbet is these days, though, especially the RoR experience

10 comments

Rust/Loco is unironically the most interesting framework right now.

Loco follows up the Rails formula pretty closely, and makes easier to learn Rust by taking care of a load of boilerplate code.

Concur on most interesting! I really hope it works out, but am cautious.

It is surprising to me seeing the rust web backend scene; many libraries, server frameworks, and users, but they are all Flask-analogs, without the benefit of the reasonably-robust ecosystem Flask has. My suspicion is that people are using them for micro-services and not websites/webapps, but I haven't been able to get a straight answer on this about how people are using these tools. I.e. even though rust is my favorite overall language and see no reason it couldn't be used for web work, I still use Django.

Axos, Axum, Rocket, Diesel etc, are all IMO not in the same league as Django. My understanding is that addressing this is Loco's Raison d'etre.

Another aspect of the Rust web ecosystem: It's almost fully gone Async.

It's quite a gap really.

I'll say this, coding agents make the lack of a "batteries included" framework like rails or Django somewhat less daunting.

But "convention over code" and having a default structure / shape for projects is extremely helpful and you feel it when it's missing.

For my last small project I looked at Loco but ended up passing on it because I felt like adoption wasn't great yet. I really hope it takes off, though.

It's really hard to get away from ssjs frameworks for front-end, so for my startup it's Axum + Seaorm for "the api" and a Svelte SSR "front end" / view layer.

I think rust programers are more likely to want flask/rack than Django/rails.

I'm at a point now where I'm not even a little interested in new frameworks. I want tried and tested. Preferably something that has been around for 15 years.
I worked with Rails a lot. In my experience, every rails dev who is fanatical about how much they love Rails, also has little to no experience with strong types. Of the ones who later try types, they no longer love Rails. Personally I quit Rails entirely because of lack of types. No, RBS and Sorbet are not even close to good enough.

Also, every enterprise rails app I've seen (seven, to date) has been really poorly written/architected in a way that other backends just weren't. Even the fairly new ones felt like legacy code already.

Ruby is a strongly typed language. I think you are confusing strong typing with static typing.
And by contrast, C is static but weakly typed.
I still really like rails. It’s really fun until your codebase reaches a certain size. At that point you better have a large suite of test which becomes a problem of it self because the tests will take forever to complete.

I tried sorbet a couple of times and totally get why it’s useful but imo it’s not just lacking (e.g. compared with what can be done with TS or even a simple type system like golang) but it also removes all the fun parts of ruby / rails.

Right, the test culture is very silly to me. Thousands of tests and long CI times, basically just doing a poor invention of a compiler. Dynamic types are so much extra work.
It's not just the untyped problems, the runtime definitions of functions, properties, etc make it nearly impossible to debug unless you have the state of your production data locally. (Or you ssh into your prod server and open up a REPL, load the state and introspect everything there). Good luck debugging locally in a nice IDE. It's a horrific nightmare. I use to love Ruby until I had to debug it live.
Yeah, dynamic typing is one thing, but the magical dynamic imports into the processes globally scoped namespace add another level to debugging difficulty.

At least in Python (as a comparison example), imports are only available in the module doing the importing - it's more explicit. You'd have to really work to get the same "everything all at once" as Rails has.

What is it about large untyped codebases that make it a nightmare?
If you make a change to the return types of a function for example you have to manually find all of the different references to that function and fix the code to handle the change. Since there are no compile time errors it's hard to know that you got everything and haven't just caused a bug.
Yes, and the downsides cascade. Because making any change is inherently risky you're kind of forced not to make changes, and instead pile on. So technical debt just grows, and the code becomes harder and harder to reason about. I have this same problem in PHP although it's mostly solved in PHP 8. But touching legacy code is incredibly involved.
Especially with duck-typing, you might also assume that a function that previously returned true-false will work if it now returns a String or nil. Semantically they’re similar, but String conveys more information (did something, here’s details vs did(n’t) do something).

But if someone is actually relying on literal true/false instead of truthiness, you now have a bug.

I say this as a Ruby evangelist and apologist, who deeply loves the language and who’s used it professionally and still uses it for virtually all of my personal projects.

The best perspective I've seen is that statically typed enforcement is basically a unit test done at compile time.
Alan Kay's argument against static typing was it was too limited and didn't capture the domain logic of the sort of types you actually use at a higher level. So you leave it up to the objects to figure out how to handle messages. Given Ruby is a kind of spiritual ancestor of Smalltalk.
the problem is that nobody listened to Alan Kay and writes dynamic code the way they'd write static code but without the types.

I always liked Rich Hickey's point, that you should program on the inside the way you program on the outside. Over the wire you don't rely on types and make sure the entire internet is in type check harmony, it's on you to verify what you get, and that was what Alan Kay thought objects should do.

That's why I always find these complaints a bit puzzling. Yes in a dynamic language like Ruby, Python, Clojure, Smalltalk you can't impose global meaning, but you're not supposed to. If you have to edit countless of existing code just because some sender changed that's an indication you've ignored the principle of letting the recipient interpret the message. It shouldn't matter what someone else puts in a map, only what you take out of it, same way you don't care if the contents of the post truck change as long as your package is in it.

That's a terrible solution because then you need a bunch of extra parsing and validation code in every recipient object. This becomes impractical once the code base grows to a certain size and ultimately defeats any possible benefit that might have initially been gained with dynamic typing.
Is that a common issue? I guess I'm having a hard time imagining a scenario that would (a) come up often and (b) be a pain to fix.
Anything can return anything and you only realize it at runtime is a massive headache. When you can't keep the entire code base in your head it becomes a liability.

I never used Ruby, but Python code bases love mixing in strings that are actually enums and overloading functions that accept all kinds of types. You just had to hope that the documentation was correct to avoid a crash.

Java 1.7 to Python feels very freeing from all the boilerplate. Kotlin, or any other modern language with a well designed standard library, to Python just feels like a bunch of extra mental work and test to write to just avoid brackets.

When CI/CD becomes your compiler you are in a tough spot
It makes coming up to speed on an existing codebase a slog because you have to trace through everything back to its source. Oh, and because there are magic methods and properties galore, your normal introspection tools in e.g. RubyMine get frequently stymied.
Same. Also became a .net developer after almost 20 years of Ruby/Rails.

Nowadays C# is anyways much more expressive than before. Meanwhile Ruby is still very slow.

Not to mention how poorly maintained are most Rails projects. People have been "vibe coding" forever.

A well-organized and maintained Rails app is great though. I'd definitely consider working with it again, but it really depends on what company it is.

https://ihp.digitallyinduced.com/ is a rails-inspired framework for Haskell that tries to get the best of both worlds. Highly recommend trying it if you have a weekend
Also the language/framework is mattering less by the day with agentic coding.

Why would anyone ever choose ruby, python, etc when you don’t need to write it?

These languages are undoubtedly dead as of now. Python may live on in ML for a bit but probably not much longer

Ruby has "undoubtedly" been dead for at least a decade now, or so I've heard. Glad to hear Python is undoubtedly joining the party.
Least informed comment Ive read here
"Agentic coding has been viable for the last decade"
Is this still a valid take after the study that came out,showing how Ruby performs way better with agentic coding?
I’ve worked in two places now with Ruby Sorbet servers. Ruby always drives me nuts how things are just in-scope and we don’t know why or where they came from.

I certainly wouldn’t want to go back to working in dynamic languages without typing on top. That takes too much brain power, I’m too old for that now.

I would say Sorbet seems more “basic” than something like Typescript. It handles function calls matching signatures, potential nulls, making sure properties actually exist, that kind of thing. Whereas TS can get quite abstract, and at times you’re fighting to convince it that a value actually is the type you say it is.

TS is very powerful and expressive, to the point that it’s possible to do computation within type code. I’m not convinced I always need that power, or that it’s always more help than hindrance.

> Ruby always drives me nuts how things are just in-scope and we don’t know why or where they came from.

irb(main):005:0> Foo.new.method(:bar).source_location => ["tmp/test.rb", 5]

You appear to be shadow banned. Letting you know since I didn't see anything egregious on a quick scan. Maybe contact HN and plead your case.

I vouched for your reply below, and to answer in the meantime:

Yes, it's runtime, but that only matters if your code can't be initialized without unacceptable side effects.

In which case you don't have a functioning test suite either, and have much larger problems.

Otherwise, just load the code you struggle to figure out into irb, or pry, or a simple test script, and print out source-location.

If that is impossible (aside from the fact that codebase is broken beyond all reason), the marginally harder solution is to use ruby-lsp[1] and look up the definitions.

This is only hard if you insist on refusing to use the available - and built in, in the case of source_location - tooling.

1: https://shopify.github.io/ruby-lsp/

> I certainly wouldn’t want to go back to working in dynamic languages without typing on top. That takes too much brain power, I’m too old for that now.

> and at times you’re fighting to convince it that a value actually is the type you say it is.

Might just be allocating that brain power to the same task but calling it a different thing.

Are you hand coding?
Are we that far gone that "hand coding" is a term now? I hope there's an /s missing
I hope "hand coding" is an antonym for "convention coding" or something.
I’m guessing hand coding means, not vibe coding.

Did you use AI? .. Nah I hand coded it.

Well, yes. I was trying to comment in spirit of parent comment.
Real programmers use butterflies. https://xkcd.com/378/
Doesn't matter because LLMs also benefit greatly from typed code bases in that they can run the type checker and fix the problems themselves on a loop.
I haven’t seen much discussion about this point other than “llm handle languages x y and z because there’s a lot of training data”. Watching Terence Tau using llm for writing proofs in Lean was a real eye opener in this regard.
Both Claude and Codex handle Ruby just fine.