4

ActiveRecordオブジェクトの配列に対して操作を実行するRubyのActiveRecordオブジェクトのカスタムゲッター関数を定義するにはどうすればよいですか?

たとえば、オブジェクトの配列の加重平均を返したいと思います。したがって、フィールドamount(100、200、300)とdefault_rate(.1、.2、.3)を持つLoanオブジェクト(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ゲッターを「オーバーロード」して、単一のLoanオブジェクトではなくLoanオブジェクトの配列でゲッターを呼び出すときに関数を定義できるようにするにはどうすればよいですか?

ローンテーブルには、amount id、amount、および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

合計IDの赤いシャツのIDが生成されます(Shirt.sumがすべてのIDの合計を生成するのとは対照的です)。

BLをこのようにレイアウトすると、BLがすっきりするだけでなく、再利用できるようになります(どんなに複雑であっても、任意のスコープで#sumメソッドを呼び出すことができ、有効な結果が得られます)。それに加えて、それは読みやすく、魔法を必要としないように見えます(まあ、ほとんど:)

これでコードが少しきれいになることを願っています:)

于 2012-11-09T18:08:17.220 に答える