Use Concerns in Rails

When it comes that you need to reuse some functions across different models in Rails, Concern comes to rescue.

In our example, we have a few stats models which all have result_on field, which needs to be calculated from passed params (start_time and end_time).
In StatsExtentions module, we define the calculation.

module StatsExtensions
  extend ActiveSupport::Concern

    def self.during(start_time = nil, end_time = nil)
      if start_time.is_a?(Range)
        end_time   = start_time.max
        start_time = start_time.min
      else
        start_time = -DateTime::Infinity.new if start_time.blank?
        end_time   =  DateTime::Infinity.new if end_time.blank?
      end

      where(result_on: start_time..end_time)
    end
  end
end

In those models, we also have common fields that we do generic sql calculations on as well. For example, they all have bids and total columns. We need to get sum of those fields. So inside include do block, we add:

def self.build_sum
  select(
        arel_table[:bids].sum.as('bids'),
        arel_table[:total].sum.as('total'),
      )
    end

def self.sum_staticstis
  build_sum.group_by_segment
end

The build_sum function select all the fields that need to be sum on and sum_staticstis group them differently based on the model definition. It allows each model has its own field structure and can still leverage the common summation function.

In one of our models, we need to include this module:

class AccountStat < ActiveRecord::Base
  include StatsExtensions

  scope :group_by_segment, -> { group(:account_id) }
end

group_by_segment scope is what sum_statistics references. This way we could call AccountStat.sum_statistics, which would coalesce bids and total fields and group by account_id.