Calculated Attribute Pattern for Ruby Models

Sometimes in a Rails application a model has attributes whose values need to be computed. For example, at Mystery Science, we have a Viewing model that is created when a lesson has been viewed. This model is associated with actions that a teacher took with a lesson (e.g., playing a video, entering full screen mode, etc.). If we want to know how much time the teacher spent watching the videos, we need to calculate the value:

class Viewing

  def seconds_spent_watching_videos
    @seconds_spent_watching_videos ||= videos_played.sum do |play|
      play.duration
    end
  end

end

It’s advisable to at least memoize this attribute so it doesn’t have to be recalculated more than once for the same instance. But, when dealing with a collection of viewings, the calculation has to be performed on each one. That means querying is very expensive and can’t be done in SQL. Let’s store the value in the table instead:

class AddSecondsSpentWatchingVideosToViewings < ActiveRecord::Migration

  def change
    add_column :viewings, :seconds_spent_watching_videos, :float
  end

end

Now, you need a method to update that value. You might be inclined to write a calculate_seconds_spent_watching_videos method that you call before_validation. That approach works just fine but it clutters up your model; consider that there may be more other attributes like this one. Here’s a pattern that I’ve used that works really well in these cases:

class Viewing

  def seconds_spent_watching_videos(options = {})
    if options[:reload]
      @seconds_spent_watching_videos ||= videos_played.sum do |play|
        play.duration
      end
    else
      read_attribute :seconds_spent_watching_videos
    end
  end

end

Here, I’m overwritting the attribute’s accessor method to take options. If you call it normally, viewing.seconds_spent_watching_videos, you’ll get the value from the database. But if you call it with the reload option, viewing.seconds_spent_watching_videos(reload: true), you’ll get the calculated value. You still need to write_attribute at some point — a callback may still be the right place for that — but I’m of the opinion that your “getter” shouldn’t double as a “setter.”

I’ve used this pattern throughout my Rails applications and it’s been really useful. Hopefully, it’s useful for you as well.

Thanks for reading! I'm Avand.

I’ve been working on the web for over a decade and am passionate about building great products.

I devote most of my time to building Lopery, a free budgeting that helps people spend with confidence, recover from the unexpected, and achieve financial independence. I used simple (but time consuming) budgeting principles to buy my first home. Now, I'm codifying (pun intended) those same principles into an easy to use app that helps people achieve their financial goals.

My last job was with Airbnb, where I focused on internal products that helped teams measure the quality of the software they were building. I also built internal tools for employees to stay more connected, especially after the COVID-19 pandemic. Before that, I was lead engineer at Mystery Science, the #1 way in which science is now taught in U.S. elementary school classroms. For a while, I also taught with General Assembly, teaching aspiring developers the basics of front-end web development.

I was born in Boston, grew up in Salt Lake City, and spent many years living in Chicago. In 2013, I came out West to San Francisco, which I called home for almost a decade. Now, I’m based out Mariposa, in the foothills of the Sierras.

I enjoy the great outdoors and absolutely love music and dance. Cars have been an lifelong obsession of mine. I’m the proud owner of a 2002 E-250 Sportsmobile van, and he and I have enjoyed many trips to beautiful and remote parts of the Pacific North West spreading good vibes. I also have a very soft spot for magic (slight of hand, in particular). I love the feeling of being inspired and absolutely love inspiring others.

What can I do for you?

Read my other posts or get in touch: