4

如何在 Ruby 中为 ActiveRecord 对象定义自定义 getter 函数,以对 ActiveRecord 对象数组执行操作?

例如,我想返回一组对象的加权平均值。因此,如果我有带有字段数量(100、200、300)和 default_rate(.1、.2、.3)的贷款对象(1、2、3),那么使用普通的 ActiveRecord 函数 Loan.find(1).amount应该返回 100,Loan.find(2).default_rate 应该返回 0.2。

但如果我有 Loan.find(2,3).default_rate,我希望它返回默认利率的加权平均值,即 0.26。我知道我可以使用 SQL 选择语句来做到这一点,但是当我在 Loan 对象数组而不是单个 Loan 对象上调用 getter 时,如何“重载”ActiveRecord 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')

这给了我 has_many 在 LoanGroup 中未定义的错误

4

4 回答 4

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或其他返回关系的方法。

于 2012-11-09T18:21:53.860 回答
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 
于 2012-11-09T17:59:41.363 回答
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
于 2012-11-09T17:46:52.837 回答
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

将为您生成一个 sum id red shirts' ids(与 Shirt.sum 不同,将生成所有 ids 的总和)。

以这种方式布置您的 BL 不仅可以使其更清晰,而且可以重复使用(您可以在任何范围内调用 #sum 方法,无论它多么复杂,您仍然会得到有效的结果)。除此之外,它似乎很容易阅读并且不涉及任何魔法(嗯,几乎:)

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

于 2012-11-09T18:08:17.220 回答