Class: Tapioca::Dsl::Compilers::UrlHelpers
- Inherits:
-
Tapioca::Dsl::Compiler
- Object
- Tapioca::Dsl::Compiler
- Tapioca::Dsl::Compilers::UrlHelpers
- Defined in:
- lib/tapioca/dsl/compilers/url_helpers.rb
Overview
Tapioca::Dsl::Compilers::UrlHelpers generates RBI files for classes that include or extend
Rails.application.routes.url_helpers.
The compiler registers generated constants to represent the Rails route helper modules:
-
GeneratedPathHelpersModuleholds the main application's path helpers, such aspost_path. -
GeneratedUrlHelpersModuleholds the main application's URL helpers, such aspost_url. -
GeneratedMountedHelpersis a synthetic module for mounted application and engine helpers, such asmain_appandarticles. Rails exposes these helpers through an anonymous dynamic module, so the compiler creates a named RBI module that can be included or extended by classes that receive mounted helpers at runtime. It is only generated for applications that mount an engine that defines its own routes.
For mounted engines, the compiler also registers engine-scoped GeneratedPathHelpersModule and
GeneratedUrlHelpersModule constants. Mounted engine helper methods return a synthetic
GeneratedRoutesProxy subclass that includes those engine-scoped helper modules.
For example, with the following setup:
# config/application.rb
class Application < Rails::Application
routes.draw do
resource :index
mount Blog::Engine, at: "/blog", as: "articles"
end
end
app/models/post.rb
class Post
# Use `T.unsafe` so that Sorbet does not complain about a dynamic
# module being included. This allows the `include` to happen properly
# at runtime but Sorbet won't see the include. However, since this
# compiler will generate the proper RBI files for the include,
# static type checking will work as expected.
T.unsafe(self).include Rails.application.routes.url_helpers
end
this compiler will produce the following RBI files:
# generated_path_helpers_module.rbi
# typed: true
module GeneratedPathHelpersModule
include ActionDispatch::Routing::PolymorphicRoutes
include ActionDispatch::Routing::UrlFor
sig { params(args: T.untyped).returns(String) }
def articles_path(*args); end
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
end
# generated_url_helpers_module.rbi
# typed: true
module GeneratedUrlHelpersModule
include ActionDispatch::Routing::PolymorphicRoutes
include ActionDispatch::Routing::UrlFor
sig { params(args: T.untyped).returns(String) }
def articles_url(*args); end
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
end
# post.rbi
# typed: true
class Post
include GeneratedPathHelpersModule
include GeneratedUrlHelpersModule
end
# blog/config/routes.rb
Blog::Engine.routes.draw do
resources :posts
end
# blog/engine/generated_path_helpers_module.rbi
# typed: true
module Blog::Engine::GeneratedPathHelpersModule
include ActionDispatch::Routing::PolymorphicRoutes
include ActionDispatch::Routing::UrlFor
sig { params(args: T.untyped).returns(String) }
def edit_post_path(*args); end
sig { params(args: T.untyped).returns(String) }
def new_post_path(*args); end
sig { params(args: T.untyped).returns(String) }
def post_path(*args); end
sig { params(args: T.untyped).returns(String) }
def posts_path(*args); end
end
# blog/engine/generated_url_helpers_module.rbi
# typed: true
module Blog::Engine::GeneratedUrlHelpersModule
include ActionDispatch::Routing::PolymorphicRoutes
include ActionDispatch::Routing::UrlFor
sig { params(args: T.untyped).returns(String) }
def edit_post_url(*args); end
sig { params(args: T.untyped).returns(String) }
def new_post_url(*args); end
sig { params(args: T.untyped).returns(String) }
def post_url(*args); end
sig { params(args: T.untyped).returns(String) }
def posts_url(*args); end
end
# generated_mounted_helpers.rbi
# typed: true
module GeneratedMountedHelpers
sig { returns(Blog::Engine::GeneratedRoutesProxy) }
def articles; end
sig { returns(GeneratedRoutesProxy) }
def main_app; end
end
# generated_routes_proxy.rbi
# typed: true
class GeneratedRoutesProxy < ::ActionDispatch::Routing::RoutesProxy
include GeneratedPathHelpersModule
include GeneratedUrlHelpersModule
end
# blog/engine/generated_routes_proxy.rbi
# typed: true
class Blog::Engine::GeneratedRoutesProxy < ::ActionDispatch::Routing::RoutesProxy
include Blog::Engine::GeneratedPathHelpersModule
include Blog::Engine::GeneratedUrlHelpersModule
end
: [ConstantType = Module[top]]
Constant Summary collapse
- NON_DISCOVERABLE_INCLUDERS =
: Array[Module[top]]
gather_non_discoverable_includers
Constants included from Runtime::Reflection
Runtime::Reflection::ANCESTORS_METHOD, Runtime::Reflection::CLASS_METHOD, Runtime::Reflection::CONSTANTS_METHOD, Runtime::Reflection::EQUAL_METHOD, Runtime::Reflection::METHOD_METHOD, Runtime::Reflection::NAME_METHOD, Runtime::Reflection::OBJECT_ID_METHOD, Runtime::Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Runtime::Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Runtime::Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Runtime::Reflection::REQUIRED_FROM_LABELS, Runtime::Reflection::SINGLETON_CLASS_METHOD, Runtime::Reflection::SUPERCLASS_METHOD, Runtime::Reflection::SignatureBlockError, Runtime::Reflection::UNDEFINED_CONSTANT
Constants included from RBIHelper
RBIHelper::TYPE_PARAMETER_MATCHER
Constants included from SorbetHelper
SorbetHelper::FEATURE_REQUIREMENTS, SorbetHelper::SORBET_BIN, SorbetHelper::SORBET_EXE_PATH_ENV_VAR, SorbetHelper::SORBET_GEM_SPEC, SorbetHelper::SORBET_PAYLOAD_URL, SorbetHelper::SPOOM_CONTEXT
Class Attribute Summary collapse
-
.engine_mount_names ⇒ Object
readonly
: Hash[Symbol, singleton(::Rails::Engine)].
Attributes inherited from Tapioca::Dsl::Compiler
Class Method Summary collapse
-
.gather_constants ⇒ Object
: -> Enumerable[Module[top]].
Instance Method Summary collapse
-
#decorate ⇒ Object
: -> void.
Methods inherited from Tapioca::Dsl::Compiler
#add_error, #compiler_enabled?, handles?, #initialize, processable_constants, requested_constants=, reset_state
Methods included from Runtime::Reflection
#abstract_type_of, #ancestors_of, #are_equal?, #attached_class_of, #class_of, #const_source_location, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #final_module?, #inherited_ancestors_of, #method_of, #name_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #sealed_module?, #signature_of, #signature_of!, #singleton_class_of, #superclass_of
Methods included from RBIHelper
#anonymous_parameter_name?, #as_nilable_type, #as_non_nilable_type, #create_block_param, #create_kw_opt_param, #create_kw_param, #create_kw_rest_param, #create_opt_param, #create_param, #create_rest_param, #create_typed_param, #extract_type_parameters, #sanitize_signature_types, serialize_type_variable, #valid_method_name?, #valid_parameter_name?
Methods included from SorbetHelper
#sorbet, #sorbet_path, #sorbet_supports?, #sorbet_syntax_check!
Constructor Details
This class inherits a constructor from Tapioca::Dsl::Compiler
Class Attribute Details
.engine_mount_names ⇒ Object (readonly)
: Hash[Symbol, singleton(::Rails::Engine)]
210 211 212 |
# File 'lib/tapioca/dsl/compilers/url_helpers.rb', line 210 def engine_mount_names @engine_mount_names end |
Class Method Details
.gather_constants ⇒ Object
: -> Enumerable[Module[top]]
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/tapioca/dsl/compilers/url_helpers.rb', line 214 def gather_constants return [] unless defined?(Rails.application) && Rails.application # Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614). routes_reloader = Rails.application.routes_reloader routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded) url_helpers_module = Rails.application.routes.named_routes.url_helpers_module path_helpers_module = Rails.application.routes.named_routes.path_helpers_module Object.const_set(:GeneratedUrlHelpersModule, url_helpers_module) Object.const_set(:GeneratedPathHelpersModule, path_helpers_module) @engine_mount_names = mounted_engine_names engine_helper_modules = register_engine_route_helpers # Only synthesize the mounted helpers module when at least one mounted engine # contributes its own route helpers. A mount of a routeless engine (or an app with # no mounts) would leave nothing but `main_app`, which we don't generate on its own. # This predicate mirrors `proxied_engines` in `generate_mounted_helpers_module`. mounts_engine_with_helpers = @engine_mount_names.values.any? do |engine_class| name_of(engine_class) && engine_class.const_defined?(:GeneratedPathHelpersModule, false) end if mounts_engine_with_helpers Object.const_set(:GeneratedMountedHelpers, Module.new) end constants = all_modules.select do |mod| next unless name_of(mod) # Fast-path to quickly disqualify most cases has_helpers = url_helpers_module > mod || path_helpers_module > mod || url_helpers_module > mod.singleton_class || path_helpers_module > mod.singleton_class has_helpers ||= engine_helper_modules.any? do |engine_mod| engine_mod > mod || engine_mod > mod.singleton_class end next false unless has_helpers includes_helper?(mod, url_helpers_module) || includes_helper?(mod, path_helpers_module) || includes_helper?(mod.singleton_class, url_helpers_module) || includes_helper?(mod.singleton_class, path_helpers_module) || engine_helper_modules.any? { |engine_mod| includes_helper?(mod, engine_mod) || includes_helper?(mod.singleton_class, engine_mod) } end constants .concat(NON_DISCOVERABLE_INCLUDERS) .push(GeneratedUrlHelpersModule, GeneratedPathHelpersModule) .concat(engine_helper_modules) constants.push(GeneratedMountedHelpers) if defined?(GeneratedMountedHelpers) constants end |
Instance Method Details
#decorate ⇒ Object
: -> void
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/tapioca/dsl/compilers/url_helpers.rb', line 188 def decorate case constant when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class generate_module_for(root, constant) else # `GeneratedMountedHelpers` is only defined when an engine is mounted (see `gather_constants`). if defined?(::GeneratedMountedHelpers) && GeneratedMountedHelpers.singleton_class === constant generate_mounted_helpers_module(root) elsif engine_helper_module?(constant) generate_module_for(root, constant) else generate_url_helper_includer end end end |