Search code examples
ruby-on-railsrefactoringgetter-settervirtual-attribute

How to refactor duplicated virtual attributes?


I've run into a problem where I have virtual attributes within models that are very similar. In short, they are acting as "converters" for some of the attributes. Here is an example of some of these virtual attributes:

class Setting < ActiveRecord::Base
  validates :overtime, presence: true, numericality: { greater_than_or_equal_to: 0 }
  validates :shift_drop_cut_off, presence: true, numericality: { greater_than_or_equal_to: 0 }

  def overtime_hrs
    return 0 unless self.overtime.present?
    (self.overtime / 3600)
  end

  def overtime_hrs=(overtime_hrs)
    return 0 unless overtime_hrs.present?
    self.overtime = overtime_hrs.to_i * 3600
  end

  def shift_drop_cut_off_hrs
    return 0 unless self.shift_drop_cut_off.present?
    (self.shift_drop_cut_off / 3600)
  end

  def shift_drop_cut_off_hrs=(shift_drop_cut_off_hrs)
    return 0 unless shift_drop_cut_off_hrs.present?
    self.shift_drop_cut_off = shift_drop_cut_off_hrs.to_i * 3600
  end
end

In this case I have two columns named "overtime" and "shift_drop_cutoff". Both of these columns are integers that represent time in seconds. However, I don't want to display these attributes to the user in seconds. Instead, I want to convert them into hours. Hence, this is the purpose of the virtual attributes.

As you can see these virtual attribute getter/setters are nearly identical. Does anyone have tips on how I can refactor this?


Solution

  • Metaprogramming ftw!

    module ByHours
      extend ActiveSupport::Concern
      module ClassMethods
        def by_hours(name, base)
          define_method name do
            (send(base) || 0) / 3600
          end
          define_method "#{name}=" do |val|
            send("#{base}=", val * 3600)
          end
        end
      end
    end
    

    Then in your Setting class:

    class Setting
      by_hours :overtime_hrs, :overtime
      by_hours :shift_drop_cut_off_hrs, :shift_drop_cut_off
    end