User-defined routes missing from generated path/URL helper RBI files after upgrading to Rails 8
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
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?
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)
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?