ruby-on-rails 加载类后,`ActionController::API`祖先不会更新

v09wglhw  于 2023-05-30  发布在  Ruby
关注(0)|答案(1)|浏览(122)

我试图理解为什么ActionController::API的祖先在它的类被加载后,当一个模块被显式地添加到它的层次结构中时,它不会被更新。
我有以下铁路:

module MyModule
  class Railtie < Rails::Railtie
    initializer "mymodule.initialize" do |app|
      # Force Rails to load view templates even in API mode
      if Rails::VERSION::MAJOR >= 5
        puts ActionController::API.ancestors.include?(ActionView::Rendering)

        module ::ActionController
          module ApiRendering
            include ActionView::Rendering
          end
        end

        puts ActionController::API.ancestors.include?(ActionView::Rendering)
      end
    end
  end
end

其显示:

false
false

如果我注解了第一个puts(这触发了ActionController::API的加载),第二个puts的输出会改变:

true

在普通Ruby中,这种更新工作得很好,所以Rails必须做一些我不熟悉的事情。

module A; end
module B; end

class API
  include A
end

module A
  include B
end

API.ancestors.include?(B) # returns true

上下文:scout_apm在需要时会在jbuilder之前触发此问题,因此我设法重现了一个最小场景。

6psbrbz9

6psbrbz91#

此功能适用于ruby 3.0或更高版本(源代码)。如果您在2.7版本上运行示例代码,您将获得false作为API.ancestors.include?(B)的结果。
然而,有一种情况,即使是新的ruby版本,它也无法工作,那就是当这些模块都是ActiveSupport::Concern时,这对于ActionView::RenderingActionController::ApiRendering来说是正确的。
ActiveSupport::Concern改变了include的标准行为,并称之为“依赖管理”。当您将一个Concern包含在另一个Concern中时,它将其存储为依赖项,而不是实际包含它。稍后,当您在其他地方包含外部Concern时,首先包含这些依赖项(以前未包含的模块),然后包含实际的模块。这种行为是通过重新定义append_features方法来实现的。
如果您稍后包含另一个Concern,它会附加到依赖项中,但不会包含到现有的后代中。

require 'active_support'
module B
  extend ActiveSupport::Concern
end
module A
  extend ActiveSupport::Concern
  include B
end
class API
  include A
end
A.ancestors #[A]
B.ancestors #[B]
A.instance_variable_get(:@_dependencies) #[B]
API.ancestors #[API, A, B, Object, ...]

# if we later try to include a standard Module
# it is correctly recognized as an ancestor to the API Class,
# however adding a new Concern has no effect
module C
end
module D
  extend ActiveSupport::Concern
end
module A
  include C
  include D
end
API.ancestors #[API, A, C, B, Object, ...]

相关问题