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?
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