找不到 Ruby on Rails STI 基类的子类的关联

问题描述 投票:0回答:1

我正在为 Foreman v3.3.0 实现一个插件,该插件应该向 子网模型添加一些额外的参数。

module SubnetExtensions
  extend ActiveSupport::Concern

  included do
    has_one :subnet_bgp_config
  end
end

Subnet.include SubnetExtensions

Foreman 中的子网是通过单表继承 (STI) 和类型

Subnet::Ipv6
Subnet::Ipv4
实现的。每当呈现任何子网的页面时,我的插件问题就会出现:

在 Subnet::Ipv4 上找不到名为“subnet_bgp_config”的关联;也许你拼错了?

在 Rails 控制台中,我可以验证子类根本不存在关联:

# foreman-rake console (Rails 6.1.7)
irb(main):001:0> Subnet.reflect_on_all_associations.map(&:name).include? :subnet_bgp_config
=> true
irb(main):002:0> Subnet::Ipv4.reflect_on_all_associations.map(&:name).include? :subnet_bgp_config
=> false

我已经验证,当

has_one
关联被写入到 Foreman 源代码时,这种方法效果很好。因此,以某种方式将插件添加到 Foreman 的方法破坏了继承。有人可以提示出什么问题或指示下一步要检查/尝试什么吗?

感谢您的关注,
泽维尔。

ruby-on-rails single-table-inheritance activesupport-concern theforeman
1个回答
0
投票

问题似乎是由于 Rails 在单表继承 (STI) 中处理关联和继承的方式造成的,并在运行时添加了关注点。具体来说,当使用

ActiveSupport::Concern
将新关联注入 STI 基类 (
Subnet
) 时,该关联可能不会自动传播到子类(
Subnet::Ipv4
Subnet::Ipv6
)。发生这种情况是因为子类加载后包含的关注点可能无法正确注册新关联。

以下是解决该问题的一些方法:

解决方案 1:在
prepend
 中使用 
SubnetExtensions

使用插件扩展

include
类时,请使用
prepend
,而不是
Subnet
。这有助于确保在 Rails 处理 STI 子类之前添加关联。

module SubnetExtensions
  extend ActiveSupport::Concern

  included do
    has_one :subnet_bgp_config
  end
end

# Prepend to make sure it applies before subclasses are loaded
Subnet.prepend SubnetExtensions

解决方案 2:直接在插件中的子类上定义关联

如果

prepend
不能解决问题,您可以显式地将关联添加到插件中的每个子类,确保 Rails 识别每个子类的关联:

module SubnetExtensions
  extend ActiveSupport::Concern

  included do
    has_one :subnet_bgp_config
  end
end

Subnet.include SubnetExtensions
Subnet::Ipv4.include SubnetExtensions
Subnet::Ipv6.include SubnetExtensions

这显式地将

has_one :subnet_bgp_config
添加到
Subnet::Ipv4
Subnet::Ipv6
,直接解决每个子类的问题。

解决方案 3:使用
Rails.application.config.to_prepare

如果

SubnetExtensions
需要在运行时加载(例如,如果插件在 Rails 初始化后加载),则尝试使用
Rails.application.config.to_prepare
将其添加到 Rails 初始值设定项中。这可确保在开发中的每个请求上重新加载插件,从而允许正确应用关联。

在初始化程序文件中,添加:

# config/initializers/subnet_extensions.rb
Rails.application.config.to_prepare do
  Subnet.include SubnetExtensions
  Subnet::Ipv4.include SubnetExtensions
  Subnet::Ipv6.include SubnetExtensions
end

这种方法在开发环境中特别有用,在开发环境中,Rails 类可能会重新加载,并且每次都需要重新包含插件代码。

解决方案 4:使用
after_initialize
回调

如果以上方法都不起作用,您可以考虑在

after_initialize
中使用
SubnetExtensions
回调来确保在 Foreman 和 Rails 完全加载后定义关联。

在插件的主文件或初始化程序中:

Rails.application.config.after_initialize do
  Subnet.include SubnetExtensions
end

此回调可确保在 Rails 初始化类后加载并应用

SubnetExtensions
。然而,这通常是最后的手段,因为它取决于初始化顺序。

总结

按顺序尝试每个解决方案,因为

prepend
并在每个子类上添加
include
通常更清晰,并且应该足够了。如果您仍然遇到问题,
Rails.application.config.to_prepare
after_initialize
可以帮助确保您的插件在 Foreman 的上下文中正确初始化。

© www.soinside.com 2019 - 2024. All rights reserved.