我正在为 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 的方法破坏了继承。有人可以提示出什么问题或指示下一步要检查/尝试什么吗?
感谢您的关注,
泽维尔。
问题似乎是由于 Rails 在单表继承 (STI) 中处理关联和继承的方式造成的,并在运行时添加了关注点。具体来说,当使用
ActiveSupport::Concern
将新关联注入 STI 基类 (Subnet
) 时,该关联可能不会自动传播到子类(Subnet::Ipv4
和 Subnet::Ipv6
)。发生这种情况是因为子类加载后包含的关注点可能无法正确注册新关联。
以下是解决该问题的一些方法:
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
如果
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
,直接解决每个子类的问题。
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 类可能会重新加载,并且每次都需要重新包含插件代码。
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 的上下文中正确初始化。