配置成功:has_many后,反向:belongs_to关联返回nil问题排查
ForemanCnames插件关联问题排查与解决
问题背景
正在为Foreman v3.5.1开发ForemanCnames插件,用于为网络接口模型(Nic::Base)添加CNAME记录支持。期望的关联关系中,红色箭头表示当前失效的连接:
问题描述
可从已知NIC获取对应CNAME,但无法从给定CNAME获取对应的NIC或Domain:
irb(main):001:0> n = Nic::Base.find(7958) => <Nic::Managed id: 7958, name: "satellite-test-01.scc.kit.edu", ...> irb(main):002:0> a = n.host_aliases.first => <ForemanCnames::HostAlias id: 5, name: "newer-alias", nics_id: 7958, domains_id: 1> irb(main):004:0> a.nic => nil irb(main):006:0> a.domain => nil irb(main):007:0> n.domain => <Domain id: 1, name: "scc.kit.edu", ...>
Rails模型定义
HostAlias模型代码
module ForemanCnames class HostAlias < ActiveRecord::Base belongs_to :domain belongs_to :nic, :class_name => '::Nic::Base', :inverse_of => :host_aliases validates :nics_id, :presence => true validates :name, :presence => true, :uniqueness => {:scope => :domains_id} def to_s name end def cname Nic::Interface.find(nics_id).fqdn end end end
Nic::Base关联扩展(Concern)
module ForemanCnames::Concerns::NicExtensions extend ActiveSupport::Concern included do has_many :host_aliases, :foreign_key => :nics_id, :class_name => 'ForemanCnames::HostAlias', :inverse_of => :nic accepts_nested_attributes_for :host_aliases, allow_destroy: true end end
数据库迁移文件
class CreateHostAliases < ActiveRecord::Migration[4.2] def change create_table :foreman_cnames_host_aliases do |t| # 默认添加主键id列 t.string :name, :limit => 255 t.references :nics, null: false, foreign_key: true t.references :domains, null: false, foreign_key: true t.timestamps end end end
未给Domain添加关联,因无需通过Domain查找CNAME。
问题分析
- HostAlias#cname功能正常:
irb(main):001:0> ForemanCnames::HostAlias.first.cname => "satellite-test-01.scc.kit.edu"
- 关联方法返回nil:理论上
HostAlias#nic应通过Nic::Base.find(nics_id)实现,但Rails自动生成的关联方法返回nil,且无法查看对应SQL:
irb(main):001:0> ForemanCnames::HostAlias.first.method(:nic).source_location => ["/usr/share/gems/gems/activerecord-6.1.7/lib/active_record/associations/builder/association.rb", 102] irb(main):002:0> ForemanCnames::HostAlias.first.nic.to_sql Traceback (most recent call last): 2: from lib/tasks/console.rake:5:in `block in <top (required)>' 1: from (irb):2 NoMethodError (undefined method `to_sql' for nil:NilClass) irb(main):003:0> puts Nic::Base.find(7958).host_aliases.to_sql SELECT "foreman_cnames_host_aliases".* FROM "foreman_cnames_host_aliases" WHERE "foreman_cnames_host_aliases"."nics_id" = 7958
- :inverse_of配置无效:添加前后问题未改善。
- 关联反射检查:发现
:nic和:domain的外键被识别为nic_id和domain_id,但数据库表实际字段为nics_id和domains_id,这是问题根源:
irb(main):001:0> ForemanCnames::HostAlias.reflect_on_association(:nic).association_primary_key => "id" irb(main):002:0> ForemanCnames::HostAlias.reflect_on_association(:nic).foreign_key => "nic_id" irb(main):003:0> ForemanCnames::HostAlias.reflect_on_association(:domain).association_primary_key => "id" irb(main):004:0> ForemanCnames::HostAlias.reflect_on_association(:domain).foreign_key => "domain_id" irb(main):005:0> ForemanCnames::HostAlias => ForemanCnames::HostAlias(id: integer, name: string, nics_id: integer, domains_id: integer)
解决方案
在HostAlias模型的belongs_to关联中显式指定外键,匹配数据库表中的字段名:
module ForemanCnames class HostAlias < ActiveRecord::Base # 显式指定外键为domains_id belongs_to :domain, foreign_key: :domains_id # 显式指定外键为nics_id belongs_to :nic, class_name: '::Nic::Base', inverse_of: :host_aliases, foreign_key: :nics_id validates :nics_id, :presence => true validates :name, :presence => true, :uniqueness => {:scope => :domains_id} def to_s name end def cname Nic::Interface.find(nics_id).fqdn end end end
修改后,Rails将使用正确的外键字段查询关联记录,a.nic和a.domain即可返回对应对象。
补充说明
后续若需通过Host查找CNAME,可在Host::Base中添加关联:has_many :host_aliases, through: :interfaces,但此非当前问题的解决方案。
内容的提问来源于stack exchange,提问作者Xavier Mol
相关产品推荐
相关产品推荐

