# bad use of metaprogramming
module MetaTimeDSL

{:second => 1,
:minute => 60,
:hour => 3600,
:day => [24,:hours],
:week => [7,:days],
:month => [30,:days],
:year => [364.25, :days]}.each do |meth, amount|
define_method "m_#{meth}" do
amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
self * amount
end
alias_method "m_#{meth}s".intern, "m_#{meth}"
end

end
Numeric.send :include, MetaTimeDSL



# Rewrite
module TimeDSL

def second
self * 1
end
alias_method :seconds, :second

def minute
self * 60
end
alias_method :minutes, :minute

def hour
self * 3600
end
alias_method :hours, :hour

def day
self * 86400
end
alias_method :days, :day

def week
self * 604800
end
alias_method :weeks, :week

def month
self * 18144000
end
alias_method :months, :month

def year
self * 31471200
end
alias_method :years, :year

end
Numeric.send :include, TimeDSL

module RefaMetaTimeDSL

{:second => 1,
:minute => 60,
:hour => 3600,
:day => [24,:hours],
:week => [7,:days],
:month => [30,:days],
:year => [364.25, :days]}.each do |meth, amount|
self.class_eval <<-RUBY
def r_#{meth}
#{amount.is_a?(Array) ? "#{amount[0]}.#{amount[1]}" : "#{amount}"}
end
alias_method :r_#{meth}s, :r_#{meth}
RUBY
end

end
Numeric.send :include, RefaMetaTimeDSL

module EvalMetaTimeDSL
def self.included(base)
base.class_eval do
[ [:e_second, 1],
[:e_minute, 60],
[:e_hour, 3600],
[:e_day, [24,:e_hours]],
[:e_week, [7,:e_days]],
[:e_month, [30,:e_days]],
[:e_year, [365.25, :e_days]]].each do |meth, amount|
amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
eval "def #{meth}; self*#{amount}; end"
alias_method "#{meth}s", meth
end
end
end
end
Numeric.send :include, EvalMetaTimeDSL

module GoodMetaTimeDSL

SECOND = 1
MINUTE = SECOND * 60
HOUR = MINUTE * 60
DAY = HOUR * 24
WEEK = DAY * 7
MONTH = DAY * 30
YEAR = DAY * 364.25

%w[SECOND MINUTE HOUR DAY WEEK MONTH YEAR].each do |const_name|
meth = const_name.downcase
class_eval <<-RUBY
def g_#{meth}
self * #{const_name}
end
alias g_#{meth}s g_#{meth}
RUBY
end
end
Numeric.send :include, GoodMetaTimeDSL



###############################
#
#
# Benchmarks
require "rubygems"
require "rbench"

TIMES = 100_000

RBench.run(TIMES) do

format :width => 65

column :times
column :bad_meta, :title => "Bad Neta"
column :no_meta, :title => "No Meta"
column :refa, :title => "Refactored"
column :eval_meta, :title => 'Refactor 2'
column :good_meta, :title => "Better Meta"

group("MetaTimeDSL") do
report "with 360.seconds" do
bad_meta { 360.m_seconds }
no_meta { 360.seconds }
refa { 360.r_seconds }
eval_meta { 360.e_seconds}
good_meta { 360.g_seconds }
end

report "with 360.minutes" do
bad_meta { 360.m_minutes }
no_meta { 360.minutes }
refa { 360.r_minutes }
eval_meta { 360.e_minutes }
good_meta { 360.g_minutes }
end

report "with 360.hours" do
bad_meta { 360.m_hours }
no_meta { 360.hours }
refa { 360.r_hours }
eval_meta { 360.e_hours }
good_meta { 360.g_hours }
end

report "with 360.days" do
bad_meta { 360.m_days }
no_meta { 360.days }
refa { 360.r_days }
eval_meta { 360.e_days }
good_meta { 360.g_days }
end
report "with 360.weeks" do
bad_meta { 360.m_weeks }
no_meta { 360.weeks }
refa { 360.r_weeks }
eval_meta { 360.e_weeks }
good_meta { 360.g_weeks }
end
report "with 18.months" do
bad_meta { 18.m_months }
no_meta { 18.months }
refa { 18.r_months }
eval_meta { 18.e_months }
good_meta { 18.g_months }
end
report "with 7.years" do
bad_meta { 7.m_years }
no_meta { 7.years }
refa { 7.r_years }
eval_meta { 7.e_years }
good_meta { 7.g_years }
end

end
end

#####
#
#                                  | Bad Neta | No Meta | Refactored | Refactor 2 | Better Meta |
# --MetaTimeDSL----------------------------------------------------------------------------------
# with 360.seconds         x100000 |    0.143 |   0.046 |      0.033 |      0.047 |       0.048 |
# with 360.minutes         x100000 |    0.152 |   0.045 |      0.033 |      0.046 |       0.046 |
# with 360.hours           x100000 |    0.127 |   0.044 |      0.032 |      0.045 |       0.048 |
# with 360.days            x100000 |    0.144 |   0.047 |      0.070 |      0.045 |       0.048 |
# with 360.weeks           x100000 |    0.129 |   0.044 |      0.070 |      0.046 |       0.047 |
# with 18.months           x100000 |    0.143 |   0.045 |      0.071 |      0.045 |       0.048 |
# with 7.years             x100000 |    0.143 |   0.046 |      0.089 |      0.054 |       0.050 |
#
####