如何为Ruby中的ActiveRecord对象定义自定义getter函数,这些函数将对ActiveRecord对象的数组执行操作?
例如,我想在一个对象数组上返回加权平均值。因此,如果我有贷款对象(1,2,3)的字段数量(100,200,300)和default_rate(。1,.2,.3),那么使用正常的ActiveRecord函数Loan.find(1).amount应该返回100,Loan.find(2).default_rate应该返回.2。
但如果我有Loan.find(2,3).default_rate,我希望它返回默认费率的加权平均值,即.26。我知道我可以使用SQL select语句来做到这一点,但我怎么能超载" ActiveRecord getter允许在我在Loan对象数组而不是单个Loan对象上调用getter时定义一个函数?
贷款表包含字段金额id,金额和default_rate
class Loan < ActiveRecord::Base
module Collection
def default_rate
sum(:default_rate * :amount) / sum(:amount)
end
end
end
class LoanGroup
has_many :loans, :extend => Loan::Collection
end
#Then I try
obj = LoanGroup.where('id < 10')
这给了我在LoanGroup
中未定义has_many的错误答案 0 :(得分:2)
为了避免使用特定于Array
记录集合的方法来污染Loan
的命名空间,您可以为这样的集合创建一个包装器并在其上调用您的方法。
类似的东西:
class LoanArray < Array
def initialize(*args)
find(*args)
end
def find(*args)
replace Array.wrap(Loan.find(*args))
self
end
def default_rate
if length == 1
self[0].default_rate
else
inject(0) {|mem,loan| mem + loan.defualt_rate } / count
end
end
end
# then
arr = LoanArray.new(2,3).default_rate #=> 0.26
arr.find(1,2,3).default_rate #=> 0.2
arr.length #=> 3
arr.find(1) #=> [<Loan id=1>]
arr.default_rate #=> 0.1
arr.length #=> 1
以下原始答案:使用关联扩展名
使用关联扩展程序。这样,无论是贷款还是贷款集合,该方法都是相同的。
class MyClass
has_many :loans do
def default_rate
sum(:default_rate) / count
end
end
end
obj = MyClass.find(1)
obj.loans.first.default_rate #=> 0.1
obj.loans.default_rate #=> 0.2
obj.loans.where(:id => [2,3]).default_rate #=> 0.26
如果你想保留Loan
类贷款的逻辑,你也可以在那里写下扩展名,例如:
class Loan
module Collection
def default_rate
sum(:default_rate) / count
end
# plus other methods, as needed, e.g.
def average_amount
sum(:amount) / count
end
end
end
class MyClass
has_many :loans, :extend => Loan::Collection
end
编辑:因为@Santosh指出association.find
没有返回关系,所以它在这里不起作用。您必须使用where
或其他返回关系的方法。
答案 1 :(得分:1)
如果我理解你的问题,你可以为此编写课程方法。
class Loan
def self.default_rate
sum = 0
all.each do |loan|
sum += loan.default_rate
end
sum / all.count
end
end
然后
Loan.where(:id => [2, 3]).default_rate
其他解决方案如果你想使用Loan.find(2,3),那么你需要覆盖Array类。
class Array
def default_rate
sum = 0
self.each do |loan|
sum += loan.default_rate
end
sum / self.count
end
end
Loan.find(2, 3).default_rate
答案 2 :(得分:0)
如果不将此作为一种类方法,你可能会有更多的自由;您可以使用default_rate (*args)
的方法标头,以允许使用splat运算符传递多个参数,然后检查args
长度。它可以很好地递归地完成:
def default_rate(*args)
sum = 0
if args.length == 1
return array_name[arg-1] # guessing that's how you store the actual var
else
args.each do |arg|
sum += default_rate arg
end
end
return sum/args.length
end
答案 3 :(得分:0)
如果您接受引入命名范围,那么您可以利用事实范围来定义它们所定义的类的方法。这样,有了
class Shirt < ActiveRecord::Base
scope :red, where(:color => 'red')
def self.sum
# consider #all as the records you are processing - the scope will
# make it return neccessary ones only
all.map(&:id).sum
end
end
Shirt.red.sum
将为您提供一个总和ID红色衬衫'ID(而不是Shirt.sum将产生所有ID的总和)。
让BL以这种方式布局不仅可以使其更清晰,而且还可以重复使用它(您可以在任何范围内调用#sum方法,无论它有多复杂,您仍然可以获得有效的结果)。除此之外,它似乎很容易阅读并且不涉及任何魔法(好吧,差不多:)
希望这会让你的代码清理一下:)