Using Getter for ActiveRecord in an Array of Objects

How can I define custom getter functions for ActiveRecord objects in Ruby that will perform operations on an array of ActiveRecord objects?

For example, I would like to return weighted averages to an array of objects. Therefore, if I have Loan objects (1,2,3) with the sums of the fields (100, 200, 300) and default_rate (.1, .2, .3), then with the usual functions ActiveRecord Loan.find (1). amount should return 100, Loan.find (2) .default_rate should return .2.

But if I had Loan.find (2,3) .default_rate, I would like it to return the default weighted average of bids, which is .26. I know that I can do this with SQL select statements, but how can I "overload" the getter ActiveRecord to allow the function to be defined when I call getter in an array of Loan objects, and not in a single Loan object?

The loan table shows the quantity, quantity, and default value.

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') 

This gives me an error that has_many is undefined in LoanGroup

+4
source share
4 answers

To avoid polluting the Array namespace with methods specific to Loan record collections, you can create a wrapper for such a collection and call your methods on it.

Sort of:

 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 

Original answer below: using association extension

Use the association extension. Thus, the method will be the same, whether on a loan or a collection of loans.

 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 

If you want to keep the logic of loans in the Loan class, you can also write the extension there, for example:

 class Loan module Collection def default_rate sum(:default_rate) / count end # plus other methods, as needed, eg def average_amount sum(:amount) / count end end end class MyClass has_many :loans, :extend => Loan::Collection end 

Edit: as @Santosh points out, association.find does not return a relationship, so it will not work here. You will need to use where or some other method that returns the relation.

+2
source

If I understand your question correctly, you can write a class method for this.

 class Loan def self.default_rate sum = 0 all.each do |loan| sum += loan.default_rate end sum / all.count end end 

Then

 Loan.where(:id => [2, 3]).default_rate 

Another solution, if you want to use Loan.find (2,3), then you need to override the Array class.

 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 
+1
source

You may have a little more freedom if you do not do this as a class method; you may have a default_rate (*args) method header to allow multiple arguments to be passed using the splat operator, and then check the length of the args . This can be done quite recursively:

 def default_rate(*args) sum = 0 if args.length == 1 return array_name[arg-1] # guessing that how you store the actual var else args.each do |arg| sum += default_rate arg end end return sum/args.length end 
0
source

If you accept the input of a named scope, than you can use the fact that the scope has the methods of the classes on which they are defined. So having

 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 

will give you the id id of the red shirts ID (unlike Shirt.sum will give the sum of all the identifiers).

Having executed your BL, this method not only makes it cleaner, but also allows you to reuse it (you can call the #sum method in any area, no matter how complicated it is, and you will still get real results). In addition to this, it seems easy to read and contains no magic (well, almost :)

Hope you clear the code a bit :)

0
source

Source: https://habr.com/ru/post/1445110/


All Articles