Search code examples
mysqlruby-on-railsrubyactiverecordmethod-missing

How to add a property/use method_missing approach to an ActiveRecord object in Rails?


I'm querying a mysql database and returning an array of objects created by a join. From what I understand, the ActiveRecord select command returns an array of new objects, each with the properties of both tables in the join.

I want to perform some calculations on each of these objects, and associate a few attributes with the object. I understand that if this was my class, I could use the method_missing approach as seen here. However, since this object is created by the select command, I don't have class code where I can define the add_attrs function. Is this possible? Is using method_missing the correct approach and if so, how do I implement it?

Here is the initial query which returns an array of ActiveRecord objects.

def self.select_with_location
    select("advertisements.id, advertisements.weight, locations.latitude, locations.longitude")
end

If I were to go to the ruby console and type the commands

advertisements = Advertisement.joins(:locations).select_with_location
puts advertisements[0].inspect

I should get something like:

{id: 0, weight: 5, locations.latitude: 49.03030, locations.longitude: 50.5050}

Now, what I want is to iterate through the advertisements and perform a calculation on each of the weights. I want to store the results of that calculation into a new attribute of advertisement, which I want to call weight_multiplier. After this calculation, if I go to the ruby console and type the following command:

puts advertisements[0].inspect

I would want to get the result

{id:0, weight: 5, locations.latitude: 49.03030, locations.longitude: 50.5050, weight_multiplier: 0.446003}

In this example the numbers themselves aren't important, I just want to add the weight_multiplier attribute to each of the advertisements in the array.

I know the following code is incorrect, but this is the gist of what the function will do:

def self.assign_weight_multiplier advertisements
    totalWeights = advertisements.inject(0){|sum,advertisement| sum + advertisement.weight }
    advertisements.each do |advertisement|
      advertisement.weight_multiplier = (Float(advertisement.weight)/Float(totalWeights))
    end
    advertisements
end

Solution

  • You can add 2 new methods to your Advertisement class

    def total_weight
      @total_weight ||= Advertisement.sum(:weight)
    end
    
    def weight_multiplier
      self.weight.to_f / total_weight
    end
    

    then you can call weight_multiplier as a instance method on Advertisement instance