tapioca
tapioca copied to clipboard
Make `UrlHelpers` generator Rails Engine aware
It turns out that Rails Engines have their own router and the controllers, etc inside a Rails Engine get an engine specific url_helper module included, not the Rails.application one.
In order to be able to properly represent this, we need to do the following:
- Loop over all the Rails engines inside the application
- For each engine:
- Grab the helpers modules
engine.routes.named_routes.url_helpers_moduleandengine.routes.named_routes.path_helpers_moduleand give them engine specific names, likeXXX::Engine::GeneratedUrlHelpersModule. - Discover all the named modules that include those url helper modules and gather them as constants to decorate.
- In
decorate, create module definitions for the app and engine url helpers and add includes for them for the modules that include them.
- Grab the helpers modules
Test Case
module Article
class Engine < ::Rails::Engine
isolate_namespace Article
routes.draw do
resource :articles
end
end
end
class Application < Rails::Application
routes.draw do
resource :index
mount Article::Engine, at: "/", as: "articles"
end
end
should create:
# generated_path_helpers_module.rbi
# typed: strong
module GeneratedPathHelpersModule
include ::ActionDispatch::Routing::UrlFor
include ::ActionDispatch::Routing::PolymorphicRoutes
sig { params(args: T.untyped).returns(String) }
def edit_index_path(*args); end
sig { params(args: T.untyped).returns(String) }
def index_path(*args); end
sig { params(args: T.untyped).returns(String) }
def new_index_path(*args); end
sig { params(args: T.untyped).returns(String) }
def articles_path(*args); end
end
# generated_url_helpers_module.rbi
# typed: strong
module GeneratedUrlHelpersModule
include ::ActionDispatch::Routing::UrlFor
include ::ActionDispatch::Routing::PolymorphicRoutes
sig { params(args: T.untyped).returns(String) }
def edit_index_url(*args); end
sig { params(args: T.untyped).returns(String) }
def index_url(*args); end
sig { params(args: T.untyped).returns(String) }
def new_index_url(*args); end
sig { params(args: T.untyped).returns(String) }
def articles_url(*args); end
end
# article/engine/generated_path_helpers_module.rbi
# typed: strong
module Article::Engine::GeneratedPathHelpersModule
include ::ActionDispatch::Routing::UrlFor
include ::ActionDispatch::Routing::PolymorphicRoutes
sig { params(args: T.untyped).returns(String) }
def edit_article_path(*args); end
sig { params(args: T.untyped).returns(String) }
def article_path(*args); end
sig { params(args: T.untyped).returns(String) }
def new_article_path(*args); end
sig { params(args: T.untyped).returns(String) }
def articles_path(*args); end
end
# article/engine/generated_url_helpers_module.rbi
# typed: strong
module Article::Engine::GeneratedUrlHelpersModule
include ::ActionDispatch::Routing::UrlFor
include ::ActionDispatch::Routing::PolymorphicRoutes
sig { params(args: T.untyped).returns(String) }
def edit_article_url(*args); end
sig { params(args: T.untyped).returns(String) }
def article_url(*args); end
sig { params(args: T.untyped).returns(String) }
def new_article_url(*args); end
sig { params(args: T.untyped).returns(String) }
def articles_url(*args); end
end
and the corresponding includes to the modules that include the above helpers.
It turns out that for the above test case also need to generate:
module GeneratedMountedHelpers < ActionDispatch::Routing::RouteSet::MountedHelpers
sig { returns(Article::Engine::RoutesProxy) }
def articles; end
end
with:
class Article::Engine::GeneratedRoutesProxy < ActionDispatch::Routing::Proxy
include Article::Engine::GeneratedPathHelpersModule
include Article::Engine::GeneratedUrlHelpersModule
end
and the GeneratedMountedHelpers module being mixed into all controllers as a helper module.
Btw, the way to discover all this info about Rails engines is:
$ dev c
👩💻 Running bin/console from dev.yml
[1] pry(main)> require "rails"
=> true
[2] pry(main)> module Article
class Engine < ::Rails::Engine
isolate_namespace Article
routes.draw do
resource :articles
end
end
end
class Application < Rails::Application
routes.draw do
resource :index
mount Article::Engine, at: "/", as: "articles"
end
end
=> nil
[3] pry(main)> Rails.application.railties.grep(Rails::Engine)
=> [#<Article::Engine:0x00007f80193c0d48
@_all_autoload_paths=nil,
@_all_load_paths=nil,
@app=nil,
@app_build_lock=#<Thread::Mutex:0x00007f80193c0be0>,
@config=
#<Rails::Engine::Configuration:0x00007f80194f3c10
@generators=
#<Rails::Configuration::Generators:0x00007f80194f38a0
@after_generate_callbacks=[],
@aliases={},
@api_only=false,
@colorize_logging=true,
@fallbacks={},
@hidden_namespaces=[],
@options={},
@templates=[]>,
@javascript_path="javascript",
@middleware=#<Rails::Configuration::MiddlewareStackProxy:0x00007f80194f3648 @delete_operations=[], @operations=[]>,
@root=#<Pathname:/Users/ufuk/src/github.com/Shopify/shopify-types>>,
@env_config=nil,
@helpers=nil,
@routes=#<ActionDispatch::Routing::RouteSet:0x00007f80194f3490>>]
[4] pry(main)> Rails.application.railties.grep(Rails::Engine).first.routes.named_routes.path_helpers_module.instance_methods(false)
=> [:edit_articles_path, :new_articles_path, :articles_path]
[5] pry(main)> Rails.application.railties.grep(Rails::Engine).first.routes.named_routes.url_helpers_module.instance_methods(false)
=> [:articles_url, :edit_articles_url, :new_articles_url]
[6] pry(main)> Rails.application.routes.mounted_helpers.instance_methods(false)
=> [:_articles, :articles]