在对象数组上使用ActiveRecord getters

时间:2012-11-09 17:34:20

标签: ruby-on-rails ruby activerecord

如何为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的错误

4 个答案:

答案 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方法,无论它有多复杂,您仍然可以获得有效的结果)。除此之外,它似乎很容易阅读并且不涉及任何魔法(好吧,差不多:)

希望这会让你的代码清理一下:)