ncatlab / nlab

Source code for the nLab
https://ncatlab.org
149 stars 16 forks source link

Support Ruby 2.6 #19

Closed sattlerc closed 2 years ago

sattlerc commented 2 years ago

Even after cherry-picking commits from https://github.com/parasew/instiki/ for support of newer Ruby versions, Ruby 2.6 (with any pathc level) fails to load the nLab:

vendor/rails/activesupport/lib/active_support/dependencies.rb:184:in `require': cannot load such file -- all_pages_helper (LoadError)
sattlerc commented 2 years ago

I traced the root cause.

Controllers have optional default helper modules associated with them. For a controller app/controller/stuff_controller.rb, the Rails framework expects the associated default helper module in app/helper/stuff_helper.rb.

To handle missing default helper modules, the Rails framework handles MissingSourceFile exceptions in default_helper_module! in vendor/rails/actionpack/lib/action_controller/helpers.rb:

        def default_helper_module!
          unless name.blank?
            module_name = name.sub(/Controller$|$/, 'Helper')
            module_path = module_name.split('::').map { |m| m.underscore }.join('/')
            require_dependency module_path
            helper module_name.constantize
          end
        rescue MissingSourceFile => e
          raise unless e.is_missing? module_path
        rescue NameError => e
          raise unless e.missing_name? module_name
        end

When require_dependency does not find the specified module, Ruby up until 2.5 raises a MissingSourceFile exception here. However, in Ruby 2.6, we get a LoadError.

The call to require_dependency eventually goes down to a call to the primitive method require. This would normally raise a LoadError if an expected file is not found. However, Rails 2 overwrites the new class method of LoadError to raise a MissingSourceFile instead, which is defined as a subclass of LoadError. This "extension" is defined in vendor/rails/activesupport/lib/active_support/core_ext/load_error.rb.

The changelog of Ruby 2.6 does not mention any changes in the semantics of exception construction. After digging into the Ruby codebase, I found the responsible commit for this change of behaviour. Built-in exceptions like LoadError are now directly instantiated without a call to new (which can be overridden):

 error.c: bypass Exception.new

* error.c (rb_exc_new, rb_exc_new_str): instantiate exception
  object directly without Exception.new method call.

Redefinition of class method `new` is an outdated style, and
internal exceptions should not be affected by it.