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 am a full-stack software engineer, product designer, and teacher. I’ve been working on the web for over a decade and am passionate about building great products.

I currently work at Airbnb, where I help internal product teams stay abreast with customer feedback. Before that, I was at Mystery Science, transforming how elementary school teachers teach science. And since 2013, I’ve worked on-and-off with General Assembly, teaching aspiring developers what I know about front-end web development.

I was born in Boston, grew up in Salt Lake City, and spent many years living in Chicago. Now, I call San Francisco my home.

I’m an aspiring rock climber. I have a love affair with music and cars, especially vintage BMWs and Volkswagens. One day, I’ll buy a van and transform it into an offroad-capable camping rig.

But that’s enough about me. How can I help you?

Read my other posts or get in touch: