tapioca icon indicating copy to clipboard operation
tapioca copied to clipboard

User-defined routes missing from generated path/URL helper RBI files after upgrading to Rails 8

Open hiko1129 opened this issue 7 months ago • 3 comments

We’ve recently upgraded our application to Rails 8. After the upgrade, we noticed that our user-defined routes are no longer present in the RBI files generated by Tapioca for path and URL helpers.

Specifically, the following files appear to be affected:

  • sorbet/rbi/dsl/generated_path_helpers_module.rbi
  • sorbet/rbi/dsl/generated_url_helpers_module.rbi

It seems that only routes defined by Rails itself are still present in these files, while all our custom routes defined in config/routes.rb are missing. This was working correctly before the Rails 8 upgrade.

Rails version: 8.0.2 Tapioca version: 0.17.1 Ruby version: 3.4.3

sorbet/rbi/dsl/generated_path_helpers_module.rbi

# typed: true

# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `GeneratedPathHelpersModule`.
# Please instead update this file by running `bin/tapioca dsl GeneratedPathHelpersModule`.


module GeneratedPathHelpersModule
  include ::ActionDispatch::Routing::UrlFor
  include ::ActionDispatch::Routing::PolymorphicRoutes

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_representation_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_representation_proxy_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_direct_uploads_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_disk_service_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_notes_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_properties_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_routes_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_mailers_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_representation_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_service_blob_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_service_blob_proxy_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_storage_proxy_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_storage_redirect_path(*args); end

  sig { params(args: T.untyped).returns(String) }
  def update_rails_disk_service_path(*args); end
end

sorbet/rbi/dsl/generated_url_helpers_module.rbi

# typed: true

# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `GeneratedUrlHelpersModule`.
# Please instead update this file by running `bin/tapioca dsl GeneratedUrlHelpersModule`.


module GeneratedUrlHelpersModule
  include ::ActionDispatch::Routing::UrlFor
  include ::ActionDispatch::Routing::PolymorphicRoutes

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_representation_proxy_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_representation_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_blob_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_direct_uploads_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_disk_service_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_notes_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_properties_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_routes_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_info_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_mailers_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_representation_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_service_blob_proxy_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_service_blob_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_storage_proxy_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def rails_storage_redirect_url(*args); end

  sig { params(args: T.untyped).returns(String) }
  def update_rails_disk_service_url(*args); end
end

hiko1129 avatar May 29 '25 03:05 hiko1129

Rails internals that tapioca uses might have been modified but we didn't run into this for repos that use 8.0.2.

Contents of the files you mentioned are created here. We retrieve the methods generated by Rails on GeneratedUrlHelpersModule and GeneratedPathHelpersModule.

Can you ensure Rails upgrade and the routes are correct? Are custom _path methods available in Rails.application.routes.named_routes.path_helpers_module?

KaanOzkan avatar Jun 03 '25 13:06 KaanOzkan

I have the same issue on a fresh app. Rails.application.routes.named_routes are empty on load. You need to trigger Rails.application.routes_reloader.execute_unless_loaded to populate the routes.

Related article: https://alvincrespo.hashnode.dev/rails-8s-lazy-route-loading-devise

Looking for a workaround at the moment...

Update: So, it's definitely related to the lazy routes, but I'm not sure where the root cause of that is. My current workaround is to patch this line in Tapioca: https://github.com/Shopify/tapioca/blob/85fc0803a6c51775280664ab28cc24b61aaba768/lib/tapioca/dsl/compilers/url_helpers.rb#L109

- routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
+ routes_reloader.reload!

Update 2: My current workaround is to use the sorbet/tapioca/compilers/ directory and add the url_helpers_patch.rb file with the following content:

# frozen_string_literal: true
# typed: ignore

# This is a workaround to fix the issue where tapioca fails to generate url helpers for routes that are not loaded yet.
# See https://github.com/Shopify/tapioca/issues/2295

module UrlHelpersCompilerPatch
  def gather_constants
    return [] unless defined?(Rails.application) && Rails.application

    Rails.application.routes_reloader&.reload!
    super
  end
end

Tapioca::Dsl::Compilers::UrlHelpers.singleton_class.prepend(UrlHelpersCompilerPatch)

svyatov avatar Jun 19 '25 15:06 svyatov

I also encountered the same issue as described here.

After checking the pull request for lazy route loading, I found hints on how to work around this problem. https://github.com/rails/rails/commit/cdb283566c5d5251e2f7e663e655e57e8e10abbc#diff-03ca133f807c35460f17fad4a0a29c9e0db1dc64513affc6ab6f54383c61a954

*   Defer route drawing to the first request, or when url_helpers are called

    Executes the first routes reload in middleware, or when a route set's
    url_helpers receives a route call / asked if it responds to a route.
    Previously, this was executed unconditionally on boot, which can
    slow down boot time unnecessarily for larger apps with lots of routes.

    Environments like production that have `config.eager_load = true` will
    continue to eagerly load routes on boot.

    *Gannon McGibbon*

Using this as a reference, I tried the following configuration and executed $ TAPIOCA_DSL=1 tapioca dsl, which successfully avoided the issue.

config/environments/development.rb

  config.eager_load = ENV['TAPIOCA_DSL'].present?

jiko797torayo avatar Aug 29 '25 02:08 jiko797torayo