Ruby 3.3 on Rails 1.0
nashby.github.io
nashby.github.io
On the other hand, Amber offers a look of what a Crystal rewrite of Rails would look like
Presumably you'd run into more bugs if you tried to do more than that.
Rails does have a test suite (hopefully Rails 1.0 had a decent one?) , I guess the interesting experiment would be how much code do you have to change to get Rails 1.0 test suite to pass on ruby 3.3.
Safe to say quite a bit more than for the hello world to run!
That's not really the whole story. It was possible to generate a hello world web application server.
Would've been interesting to see if other generators, generated migrations, models, controllers, views and tests worked (scaffolding blog posts and comments).
It's definitely way less than full support for a real Rails app though!
Ruby has had a lot of syntax changes over the years that are incompatible with an older interpreter. An easy example is the 1.9 shorthand hash syntax for symbol keys `{ name: "pants" }` instead of the hashrocket syntax `{ :name => "pants" }`. More recent examples would be keyword args or pattern matching.
(Action web service was an attempt to make Rails generate SOAP APIs as that was the style du jour at the time. Fortunately, JSON came along!)
It'd be a nice regression test to have:
- Rails pegged at a particular version number and test the runtimes.
- Ruby runtime pegged at a particular version and test Rails versions
https://github.com/lloeki/minimal-rack
{rack 1 2 3, rails 3.2-7.1, sinatra 1 2 3, grape} x {ruby 2.1-3.3} x {puma, thin, webrick, ...}
I know which group I'm happy to be a part of.
This seems like a pretty minimal set of changes to get a 19 year old piece of software running.
At that time odd release numbers where development versions, until this policy changed a bit later and 1.9 became an actual "GA" release.
The 1.8 -> 1.9 is really Ruby's "Python 3" moment, as it similarly improved String encoding handling, but the breakages were much more limited and it also came with pretty significant performance improvements, so the community didn't split like Python 2 and Python 3.
If you follow Ruby's development, or watch Matz talks, you see that avoiding a Python like split is really his number 1 concern.
> but the breakages were much more limited and it also came with pretty significant performance improvements, so the community didn't split like Python 2 and Python 3.
I think perhaps the ruby community was also smaller (or at least not TOO large), had an average higher level of skill (goes with the first, usually), and was fairly committed to ruby and/or rails at the time (we loved it, there were few if any similar alternatives).
I agree that there hasn't been a similar level of backwards breaking since 1.8 => 1.9, and that if there were now it would be much more disastrous. Keyword arguments in ruby ~3.0 come closest, but still were much less disruptive than 1.8=>1.9.
I have never been a serious python user, so can't really compare to python 2=>3, but my impression is that python 2=>3 was bigger even than ruby 1.8=>1.9. *BUT* that other typical python "minor" releases may actually have fewer backwards breaking changes than typical ruby "minor" releases?
Back in 2008-2010 when Python 3 was released, Python really wasn't as huge as it is today. It probably was bigger than Ruby, but not by that much.
One datapoint (that doesn't give the full picture), Pycon 2008 had 1k attendees [0], Railsconf 1k8 [1].
IMO It's really in the early/mid 2010's with the rise of "big data" that the Python community really became huge compared to Ruby's.
> I have never been a serious python user, so can't really compare
As an ex-pythonista, now Rubyist, IMO one huge factor was that Python 2 and 3 were developed concurrently for several years, that played in the messaging. Python 3.0 was released on 2008, Python 2.7 in 2010, and Python 3 wasn't really deemed production ready until 3.2 or 3.3 if I remember correctly. So there was a 4-5 years limbo. Whereas in Ruby's case, it was very clear to the community that 1.9.x was the future.
Also Python 3 did make some absolutely necessary breaking changes (like unicode), but also some minor syntax changes that made it much harder to support both with a single codebase for questionable gains.
e.g. the `try/except`syntax was `except Class, var:` in 2.x, and `except Class as var:` in 3.x. That kind of changes made it hard for packages maintainers to support both versions. Ruby 1.9 didn't have this problem. It was much easier if not trivial to write code that worked on both Ruby 1.8 and 1.9.
> typical python "minor" releases may actually have fewer backwards breaking changes than typical ruby "minor" releases?
I haven't seriously used Python in a long time, but from occasionally glancing at release notes, they seem to regularly deprecate things. Can't say if more or less than Ruby.
[0] https://pycon.blogspot.com/2008/04/part-2-attendance-registr... [1] https://www.endpointdev.com/blog/2008/06/railsconf-2008-repo...
Oh yeah, that is hugely important, and I agree ruby does this; it's usually not hard to write code that will work on at least the last handful of ruby releases.
The keyword arg changes initially made this difficult in one particular case -- and this was seen as a problem, so they introduced the `ruby2_keywords :method_name` thing, to make it possible with an opt-in to make sure you had identical behavior in all versions, before the ruby version was released that would have made it impossible.
The meaning of deprecation changed a bit. Before 3.6 or so things were deprecated, but just left in and maybe eventually removed when they broke for good, which would rarely happen. Nowadays deprecation means it will actually be removed after two releases (years), and a lot of stuff was deprecated basically just because it was old. I don't think that's an amazing signal to send, honestly.
Ruby done a smart upgrade path - new features in 2.x and breaking changes in 1.9.x. Ruby also got a "new" VM (it wasn't new, but it was the new default VM).
Before 1.9.3 the best way to deploy ruby was Passenger with RubyEE. RubyEE was based on 1.8.7 and with all the improvements that the mainline ruby implementation got there was no reason to maintain RubyEE fork. The 2.0.0 release meant to be 100% compatible with 1.9.3 and had ABI version 1.9.1.
Also, prior 2.1.0 Ruby used versioning very different from SemVer, hence 1.9.x had multiple breaking changes in its lifetime. It didn't mean "patch" version back then.
I think the main difference is that the newer ruby was actually faster than the older ruby and gave plenty of reasons to move. The main pain point im ruby migration was that it became encoding aware. Python 3 wasn't as lucky.
The painful one is 1.8–>1.9, because that's where Ruby's string grew encoding awareness.
Going from 2.7->3.0 was not nearly as painful (mostly dealing with warnings and some code being removed from the standard library as gems that people can use if they want).
I still maintain code that is nominally 1.8 compatible and it works just fine on 3.2 (I haven't yet tested on 3.3).
What is surprising about this exercise is how little code needed to be changed on code designed for Ruby 1.8 to allow it to run on Ruby 3.3. The author doesn't say how long they spent on this, but it looks like 3–4 hours for a fairly complex piece of infrastructure code.
How much effort would be required to run Django 1.0 on Python 3.12? At least this much, I’m sure. I suspect more, but I don't know the 2->3 differences well enough to be able to estimate that.
The next part, in a ruby shop, was different. We would update all the software all the time, usually pretty painless or with one or two expected issues that are easily fixed.
There was no point when it turned to old, every app was running the most recent rails and usually ruby just days after the release
There was some pain around Ruby 1.8->1.9 because of string encodings, but no huge schism ala Python 2/3.
Did you experience both transitions or is that just your feeling based on this article? If anything, I think the article proves how relatively painless the transition was.
I know, and help maintain, Ruby code written for 1.8 that hasn't been touched and is running fine in 3.2. No breakage along the way.
To be fair, that's not always possible, but Ruby's changes haven't ever been drastic.
I've not had as much experience with Python as a programmer - more as a user of apps written in Python, and I sense there's a bigger issue there than Ruby has ever had. It seems like a frequent problem that something that works in Python 3 doesn't work with Python 2 and vice versa.
I think it's better to keep the focus style as the default outline, and uniform.
It's wild how people insist on using less-good tooling because that's what they've heard they are supposed to do... and that that's as good as they are going to get.
It's not!
If performance is very important there are better options like Rust or Go, but otherwise RoR is my go to. Pretty much everything you need is baked into the framework and it's still being actively developed. Some of the new stuff is pretty great like Turbo frames and streams.
I respect that you enjoy working in Nodejs but Nodejs frameworks are just so barebones in my experience. I haven't tried Adonis but I still remember when I was looking how to parse query params in Koa. The official docs said to use a third party lib or regex them yourself from the request string...like why would I even use a framework at that point?
It seems crazy that when a new version of Ruby comes out, developers need to hunt for runtime errors (that may or may not trigger depending on the path the application took) to see what incompatibility needs to be fixed.
Take any decent statically typed language. When a new version of the language comes out, your code will not compile on constructs or functions that are no longer called or present or used the way they should.
Indeed -- well put.
> Java is “statically typed” amd has the same issues during runtime with different JVM versions.
Nothing is ever perfect -- there can be some occational runtime incompatibilities when very old Java code is run on new VMs.
But the effort in moving between one Java version and another Java version that came out many years later is likely order of magnitudes less than if you did the same thing in Ruby. There is simply no comparison.
We basically run type checking on major hand-offs between modules using DrySchema/DryValidation, a gem that makes it easy to set that up.
Without doing this, the integrity of the codebase erodes with scale.
I've had real problems upgrading client's projects built using DryValidation prior to v1 in the past.
They changed the syntax of the validations just enough to break stuff and completely removed features like shared predicates. The upgrade process was very poorly documented with suggestions to go read random blog posts.
I think the replacement for shared predicates was supported be macros but even that is marked as likely to be removed in v2.
Just looking through some of the Dry changelogs the work BREAKING still features far more than I'm happy with. I'd be much happier to use Sorbet or an RBS based typing solution than work with Dry again.
It might have advantages over ActiveModel but ActiveModel is far better supported.
But first - why don't we point out that the bulk of the issues the author faced had nothing at all to do with types? The issues were primarily with syntax changes.
Regardless - with a statically typed, compiled language, you find these issues at compile time. With any other language you find them at runtime. Either way, you'd have to fix a whole lot before you deploy the code, and just because you prefer to find your exceptions at compile time doesn't mean that it's the best way to find them.
It is indeed the best way to find them. At compile time, you get errors for all possible paths a program will take.
For dynamic languages like Ruby you will get an error only if the program takes a path through problematic code and then Ruby will flag the error. This means a runtime error could lie latent in your codebase for many more weeks and months. Only if a rare condition triggers a code path that contains the incompatibility. This is also why refactors in languages like Ruby are more difficult and conservative. As you're never sure you fixed everything.
I've also had various occasions where code compiled successfully but no longer worked as intended.
Put another way, automated test suites give you an extremely high level of assurance when using a statically typed language. When your test suite passes in the new version Ruby, you're happy but there still could be cases left that you've not dealt with in rarely triggered code paths/conditions.
The costs of static typing are reasonable as long as you're not using a fancy dependently typed language. Ask companies that are maintaining long running software -- types help. The investment is there in the beginning, the payoff is over the lifetime of the project.
If types are complex and poorly defined, you can change them ! The compiler will help you evolve your system through type errors. If you have a poorly structured program in a dynamically type language like Ruby then it becomes more difficult to evolve your system fast and with confidence. You're always asking -- have I missed something out ?
Ultimately, I think that choice of language is one of the least significant predictors of outcomes, yet it's one of the most debated and obsessed over.
Edit: I thought it would be helpful to give examples of what I think is more important. Good CI/CD practives, good observability, robust test and staging environments, etc have been far more important in my experience than static vs dynamic language choice.
There have been times though when upgrades have been extremely painful. The massive shift between versions 2 and 3 (merb integration), the openssl shenanigans (ruby v2 -> v3), and the mimemagic / shared-mime-info licensing issues are the major ones that come to mind.
That being said, I use Elm on the front end a lot and the confidence it gives me about my code is like night and day compared to ruby (or javascript).
In theory, you're correct and type errors could be hiding out in a dynamically-typed codebase after a language version upgrade, lying in wait to take your system down.
In practice, nothing like what you're describing has ever happened to me when working in large (1M+ LOC) Ruby and Python codebases.
In my experience the difference maker is testing, not type checking. I've worked in poorly tested Java codebases where upgrading a dependency still allowed the application to compile and deploy successfully, but then at runtime things started blowing up randomly. Meanwhile with well-tested dynamically-typed code I've never had any such issues.