Cache routes.url_helpers built modules

Previously, every time this was called it would return a new module.

Every time a new controller is built, it had one of these module
included onto it (see AbstractController::Railties::RoutesHelper).
Because each time this was a new module we would end up with a different
copy being included on each controller. This would also include a
different copy on a controller than on its superclass (ex.
ApplicationController). Furthermore, each generated module also extended
the url helper modules.

    +--------------+     +-------------+
    |              |     |             |
    | path_helpers |     | url_helpers | (named routes added here)
    |              |     |             |
    +------+----+--+     +----+--+-----+
           ^    ^             ^  ^
           |    |             |  |
           |  +---------------+  |
           |  | |                |
           |  | +-----------+    |
           |  |             |    |
    +------+--+---+      +--+----+-----+
    |             |      |             |
    | (singleton) +------+ url_helpers | (duplicated for each controller)
    |             |      |             |
    +-------------+      +-----+-------+
                               ^
                               |
                               |
                     +---------+-------+
                     |                 |
                     | UsersController |
                     |                 |
                     +-----------------+

The result of this is that when named routes were added to the two
top-level modules, defining those methods became extremely slow because
Ruby has to invalidate the class method caches on all of the generated
modules as well as each controller. Furthermore because there were
multiple paths up the inheritance chain (ex. one through
UsersController's generated module and one through
ApplicationController's genereated module) it would end up invaliding
the classes multiple times (ideally Ruby would be smarter about this,
but it's much easier to solve in Rails).

Caching this module means that all controllers should include the same
module and defining these named routes should be 3-4x faster.

This method can generate two different modules, based on whether
supports_path is truthy, so those are cached separately.
This commit is contained in:
John Hawthorn 2019-12-09 16:35:31 -08:00
parent 4500ebba21
commit 37e778b6b4
1 changed files with 8 additions and 0 deletions

View File

@ -477,6 +477,14 @@ module ActionDispatch
end
def url_helpers(supports_path = true)
if supports_path
@url_helpers_with_paths ||= generate_url_helpers(true)
else
@url_helpers_without_paths ||= generate_url_helpers(false)
end
end
def generate_url_helpers(supports_path)
routes = self
Module.new do