Blog Me think, why waste time say lot word, when few word do trick

Ruby (3.3) on Rails (1.0).

Rails 8.0 has recently branched out on Github, and I found myself curious about the feasibility of running Rails 1.0 on the latest Ruby version. While I was pretty sure it wouldn’t work right off the bat, I wondered: how many modifications would be necessary to at least reach the iconic “Welcome aboard! You’re riding the Rails!” screen?

So, let’s dive in. My starting point was this Gemfile:

source "https://rubygems.org"
gem "rails", "1.0.0"

Since I knew that it would be required to make some changes to Rails gems I install it with bundle install --local. This would allow for easier modifications later on. My first attempt was running bundle exec rails --help:

/activesupport-1.2.5/lib/active_support/inflector.rb:157: syntax error, unexpected ':', expecting `then' or ',' or ';' or '\n' (SyntaxError)
        when 1: "#{number}st"
              ^

Indeed, older Ruby versions allowed the use of a colon in place of then in case/when expressions. This syntax is no longer supported, so I updated it across the codebase to match the current Ruby syntax.

Ok let’s try again with bundle exec rails --help:

cannot load such file -- parsedate (LoadError)

Oh yeah, parsedate lib that was shipped with Ruby 1.8 is not longer there. It was used to parse date strings, like so:

ParseDate.parsedate "Tuesday, July 5th, 2007, 18:35:20 UTC"
# => [2007, 7, 5, 18, 35, 20, "UTC", 2]

Not sure why it was returning an array but ok. Now I can replace it with DateTime.parse that returns DateTime object. So I’ve fixed that and tried to run it again. Ugh, another error:

rails-1.0.0/lib/rails_generator/options.rb:124: syntax error, unexpected '[', expecting '|' (SyntaxError)
...make any changes.') { |options[:pretend]| }

That’s some weird syntax. Turns out you could assign something to a hash right inside the block variable thing:

opt.on('-p', '--pretend', 'Run but do not make any changes.') { |options[:pretend]| }

meaning that whatever you pass as -p option will end up being assigned to options[:pretend]. Basically it equals to

opt.on('-p', '--pretend', 'Run but do not make any changes.') { |o| options[:pretend] = o }

Alrighty then. Rerun bundle exec rails --help:

no implicit conversion of Enumerator into Array

And it’s without any stacktrace. Great. Looks like something catches all the errors and just prints them. After some investigation I’ve found this code:

def cache
  @cache ||= sources.inject([]) { |cache, source| cache + source.map }
end

In Ruby 1.8 [].map would return an array but now it returns Enumerator object and you can concat an Array with Enumerator hence the error:

irb(main):001> [] + [].map
(irb):1:in `+': no implicit conversion of Enumerator into Array (TypeError)

It’s an easy fix though. Let’s just call .to_a on the source:

def cache
  @cache ||= sources.inject([]) { |cache, source| cache + source.to_a }
end

Are we getting there?

bundle exec rails --help

`load': cannot load such file -- config.rb (LoadError)

The code in question is

require 'rbconfig'

DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
                            Config::CONFIG['ruby_install_name'])

Makes sense, in old Ruby RbConfig could be referenced with Config constant and now it’s only RbConfig. Fixed. Does it work now?

bundle exec rails --help

Usage: /vendor/bundle/ruby/3.3.0/bin/rails /path/to/your/app [options]

Great Scott! It works! Let’s try to generate a new app:

bundle exec rails blog

undefined method `exists?' for class File

Dammit, File.exists?/FileTest.exists? were removed in Ruby 1.9. Let’s replace it with File.exist?/FileTest.exist? and try again:

bundle exec rails blog
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  components
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  script/process
      create  test/fixtures
      create  test/functional
      create  test/mocks/development
      create  test/mocks/test
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  Rakefile
      create  README
      create  app/controllers/application.rb
Cannot create Binding object for non-Ruby caller

Success! Is it though? It has generated an app but all the files are empty. And if you have a sharp eye you’ll have noticed this error:

Cannot create Binding object for non-Ruby caller

Again, no stacktracks, just some plain error. It took me some time to locate that line of code that was failing with such error but here it is:

file(relative_source, relative_destination, template_options) do |file|
  # Evaluate any assignments in a temporary, throwaway binding.
  vars = template_options[:assigns] || {}
  b = binding
  vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }

  ...
end

Believe me, I really tried to figure you what this error was about given that it’s obviously a Ruby caller but luck wasn’t there for me. Then I tried to replace binding with Kernel.binding and it worked… If you know what’s going on here please let me know! Maybe Rails were redefining binding somewhere?

Alright, let’s proceed:

bundle exec rails blog
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      ...

Finally! The app is generated, files are not empty. We’re close, I can smell it! Let’s try to start it:

 bundle exec ruby script/server -p 3001

`require': cannot load such file -- script/../config/boot (LoadError)

Sure, just some random load error. Turns out in Ruby 1.8 you could require a file with relative to current file path and now you can’t:

# In Ruby 1.8
require File.dirname(__FILE__) + '/../config/boot'

# In Ruby 1.9+
require_relative '../config/boot'

With this one fixed we can try it one more time:

bundle exec ruby script/server -p 3001

=> Booting WEBrick...

activerecord-1.13.2/lib/active_record/base.rb:708: circular argument reference - table_name (SyntaxError)

Ok this one should be trivial. The code in question:

def class_name(table_name = table_name)
  ...
end

I’m a bit surprised that this was working in Ruby 1.8. The error is pretty self-explanatory so I just renamed default argument value and continued with my life:

bundle exec ruby script/server -p 3001

=> Booting WEBrick...

`load': cannot load such file -- big_decimal.rb (LoadError)
Did you mean?  bigdecimal

Right, bigdecimal is not required by default now. I’ll spare you some time and say that there was the same issue with rexml and net-smtp gems (net-smtp is not even part of Ruby anymore and I had to add it to the Gemfile). So I fixed it and tried again:

bundle exec ruby script/server -p 3001

=> Booting WEBrick...
actionmailer-1.1.5/lib/action_mailer/quoting.rb:22: invalid multibyte escape: /[\000-\011\013\014\016-\037\177-\377]/ (SyntaxError)

Oh yeah, Ruby 1.9 did a lot of changes to string encoding (you can read more on this here) and now using raw bytes doesn’t work anymore. So I believe we can convert it to /[\x00-\x11\x13\x14\x16-\x1F\x7F-\xFF]/n and it’s going to work? Well, at least the issue was fixed (yeah, yeah who cares if we’ve just introduced some vulnerability? I don’t):

bundle exec ruby script/server -p 3001

=> Booting WEBrick...
`require': cannot load such file -- soap/rpc/driver (LoadError)

Oh ffs. It comes from action_web_service (god knows what was that back in the days) and lucky us we can remove this Rails component from our stack with this config:

# Skip frameworks you're not going to use
config.frameworks -= [ :action_web_service ]

image

bundle exec ruby script/server -p 3001

=> Booting WEBrick...
`require': cannot load such file -- soap/rpc/driver (LoadError)

rails-1.0.0/lib/rails_info.rb:8: syntax error, unexpected ')' (SyntaxError)
        map {|(name, )| name}

Cool cool, you could do .map { |(param1, )| param1 } in Ruby 1.8 to ommit the second block param. You can actually do it in Ruby 3.3 but you don’t need this extra comma:

{a: 1, b: 2, c: 3}.map { |a, | a } # => [:a, :b, :c]
# or
{a: 1, b: 2, c: 3}.map { |(a)| a } # => [:a, :b, :c]
# without
{a: 1, b: 2, c: 3}.map { |a| a } # =>[[:a, 1], [:b, 2], [:c, 3]]

And one more time…

bundle exec ruby script/server -p 3001

=> Booting WEBrick...
[2024-01-15 21:23:25] INFO  WEBrick 1.8.1
[2024-01-15 21:23:25] INFO  ruby 3.3.0 (2023-12-25) [arm64-darwin23]
[2024-01-15 21:23:25] INFO  WEBrick::HTTPServer#start: pid=98161 port=3001

Oh my God, we did it!

Screenshot 2024-01-15 at 21 24 54

If for some reason you want to check the code here it is: https://github.com/nashby/rails-from-the-past

I’m pretty sure there are way more issues to fix to make it work properly but I’m not going to do it. I’m just happy enough that I’ve got to the point where I can see this greeting screen! Fin.

And may Ruby be with you!