我有一个模型Download
,其中包含一个表downloads
。 downloads
有一个名为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
答案 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'