Calculated Attribute on Ruby Model

I was helping someone tackle a problem that required the use of a calculated property. You may not always want or be able to populate your model directly from the database. Sometimes you may to need to aggregate some data and add that to your record. Maybe you can, but more of a hobbyist than a professional Ruby developer, I don’t know how.

I tried many different things but I couldn’t get any of them right. It’s hard to solve a problem in a language that you don’t know very well and even harder when you don’t how the framework behaves. So… my friend put the problem on hold.

The problem was on hold for a whole week and that gave me time to think about it (sub-consciously at least) and I decided to give it another shot this week.

The calculated value was a sum based on the values of a child table (child as in Primary Key to Foreign Key relationship). The problem was that we had to convert the child records into actual sums. The child did not contain actual values that could be added up. Instead, it contains the record type.

Basically if the child record was of a particular type (liked 0 or not liked 1) it held a value of one. If it was the latter it had a value of negative one. The values could then be summed up and given to a property. Basically it’s a scoring system based on likes.

This problem present a couple of sub-problems that must be solved.

First there is the problem with adding a calculated property. We originally did everything right, or so we thought, but then we weren’t seen the new property arrive in the client side javascript application.

At first we thought the problem lay in adding the attribute accessor. So… we added the attribute attr_reader to the property but still no dice. I think it solved part of the problem because whenever you add a property you should provide access (read, write or both).

After digging a little deeper we found out that when RoR sends the data back (we are using render: json in our controller because it’s a web api) it does not include the calculated prop in its serialization.

That’s what happens when you add add a custom prop that is not directly tied to the database. RoR does not have that as part of its serialization.

That meant that we needed to add an override for the method as_json (you can see the code below).

Here is the code with all three problems solved. The code was actually piece-mealed from a couple of different searches, but I figured that I better post it myself if I ever want to find the whole solution in one place again. I don’t actively do Ruby (on Rails or plain), and I’ve not yet become a big enough fan to want to yet…. but you never know one day I might. And if that day ever comes I will look for this!!!!

class MyRecord < ApplicationRecord
   #this provides read access to the property
   attr_reader :points

   has_many :child_record

   #this is the calculated property
   #in this case we needed to map in order to convert for a type to value that we
   #could sum up
   def points
      values = child_records.map loop |childRecord|
         if child_record.type == 0
             1  
         else
             -1
         end
      end
 
      values.sum
   end

   #as_json will add the calculated prop to the client when it's sent as json
   #if you don't do this on your react or whatever client tech, you will not 
   #see the points attribute
   def as_json(options = { })
      # just in case someone says as_json(nil) and bypasses
      # our default...
      super((options || { }).merge({
        :methods => [:points]
     }))
   end

end

I know the solution is readily obvious once you see it, but it took me a lot of trial and error to find it.

Cheers and I hope this helps you out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: