belongs_to关联没有额外的表

时间:2012-08-10 14:40:51

标签: ruby-on-rails ruby ruby-on-rails-3

我有一个模型Download,其中包含一个表downloadsdownloads有一个名为ip_address的字段,它将ip地址存储为整数。我想设置一个IpAddress模型,但是没有ip_addresses表,所以我可以做类似的事情

Download.find(1).ip_address.to_s # '127.0.0.1'
Download.find(1).ip_address.to_i # 2130706433
Download.find(1).ip_address.downloads # SELECT * FROM downloads WHERE ip_address='2130706433'
IpAddress.find(2130706433).downloads # SELECT * FROM downloads WHERE ip_address='2130706433'

我希望它表现得像:

class Download < ActiveRecord::Base
  belongs_to :ip_address, :foreign_key => :ip_address
end

class IpAddress < ActiveRecord::Base
  set_primary_key :ip_address
  has_many :downloads, :foreign_key => :ip_address
end

但没有无用的ip地址表。

这可能吗?

修改

我发现红宝石已经有IPAddr class

所以我这样做了:

require 'ipaddr'

class Download < ActiveRecord::Base
  attr_accessible :ip, ...

  def ip
    @ip ||= IPAddr.new(read_attribute(:ip), Socket::AF_INET)
  end

  def ip=(addr)
    @ip = IPAddr.new(addr, Socket::AF_INET)
    write_attribute(:ip, @ip.to_i)
  end

  def self.for_ip(addr)
    where(:ip => IPAddr.new(addr, Socket::AF_INET).to_i)
  end

end

然后我可以做很多很酷的事情

Download.new(:ip => '127.0.0.1').save
Download.for_ip('127.0.0.1').first.ip.to_i # 2130706433

3 个答案:

答案 0 :(得分:3)

belongs_to实际上是指定两个表中对象之间的关联。但你是对的,除非你需要存储其他相关数据,否则在表中存储IP地址是相当无用的。

但是,您可以使用scopes来完成您想要的效果。您可以在下载模型中使用以下内容:

class Download < ActiveRecord::Base
  scope :for_ip, lambda { |x| where(:ip_address => x) }
end

然后你会打电话给

Download.for_ip(2130706433)

获取该IP的下载列表。

您也可以添加一个类方法:

class Download < ActiveRecord::Base
  def self.for_ip(x)
    where(:ip_address => x)  
  end  
end

如果你想从字符串转换为数字IP地址,这可能会很方便。

而且,如果你想要一个IPAddress类,你可以添加这样的方法:

class IPAddress
  def initialize(ip)
    #presumably do some stuff here
    @ip = ip
  end

  def downloads
    Download.for_ip(@ip)
  end
end

答案 1 :(得分:2)

IpAddress.find(2130706433).downloads # SELECT * FROM downloads WHERE ip_address='2130706433'

这完全是一个语义问题,但如果你没有IpAddress表,这应该会改变(例如,如果没有IpAddress表,我们如何在数据库中找到IpAddress对象2130706433 - 除非你让IpAddress成为一个容器而不是一个容器特定的单个ipaddress,否则使用类似IpAddress(2130706433).downloads的构造函数来实例化新的ipaddress。

否则,我没有看到没有IpAddress表的任何问题。为什么你需要它belongs_to,而不仅仅是另一列?

如果您希望以类似的方式访问它们,可以保留模型/对象:

class Download < ActiveRecord::Base
  ##Whatever Download-model-specific code you have...
  def ip_address
    #If nil, initialize new object. Return object.
    @ip_address ||= IpAddress(ip_address_val)
  end
end

class IpAddress
  def initialize(address)
    @value = address
  end
  def downloads
    Download.where(:ip_address_val => self.value)
  end
end

修改 您可以像要求一样覆盖访问者。您只需要小心您的代码,以特别关注您的要求。 请参阅此文档:http://ar.rubyonrails.org/classes/ActiveRecord/Base.html 在“覆盖默认访问者”部分下

基本上,如果您覆盖了值,并且您希望访问数据库值,则使用read_attribute(attr_name),因此代码可能如下所示:

class Download < ActiveRecord::Base
  ##Whatever Download-model-specific code you have...
  def ip_address
    #If nil, initialize new object. Return object.
    @ip_address ||= IpAddress(read_attribute(:ip_address))
  end
end

class IpAddress
  def initialize(address)
    @value = address
  end
  def downloads
    Download.where(:ip_address => self.value)
  end
end

如果你不小心,你的代码可能会有些混乱。

答案 2 :(得分:0)

将set_table_name“downloads”添加到您的IpAddress并删除2之间的关系它已经具有列名ip_address。

这将以下列方式为您提供查询

Download.find(1).ip_address.to_s # '127.0.0.1'
Download.find(1).ip_address.to_i # 2130706433
IpAddress.find(Download.find(1).ip_address) # SELECT * FROM downloads WHERE ip_address='2130706433'
IpAddress.find(2130706433) # SELECT * FROM downloads WHERE ip_address='2130706433'