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 work on internal products that help the company build quality software that customers love. Before that, I was at Mystery Science, transforming how elementary school teachers teach science. 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. Now, I call San Francisco my home.

I enjoy the great outdoors and absolutely love music and dance. Cars have been an lifelong obsession of mine, especially vintage BMWs and Volkswagens. I’m the very proud owner of a van, duh, and he and I plan to one day visit all the national parks and baseball stadiums. Then, off to Central America. A man can dream :)

What can I do for you?

Read my other posts or get in touch: