Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Date.new not call initialize?

I want to create a subclass of Date.

A normal, healthy, young rubyist, unscarred by the idiosyncrasy of Date's implementation would go about this in the following manner:

require 'date'

class MyDate < Date

  def initialize(year, month, day)
    @original_month = month
    @original_day = day

    # Christmas comes early!
    super(year, 12, 25)
  end

end

And proceed to use it in the most expected manner...

require 'my_date'

mdt = MyDate.new(2012, 1, 28)

puts mdt.to_s

... only to be double-crossed by the fact, that the Date::new method is actually an alias to Date::civil, which doesn't ever call initialize. In this case, the last piece of code prints "2012-01-28" instead of the expected "2012-12-25".

Dear Ruby-community, wtf is this?

Is there some very good reason for aliasing new, so that it ignores initialize, and as a result, any common sense and regard for the client's programmer's mental health?

like image 623
jst Avatar asked Jan 28 '12 22:01

jst


1 Answers

You define initialize, but you create the new instance with new. new returns a new instance of the class, not the result of initialize.

You may do:

require 'date'

class MyDate < Date

  def self.new(year, month, day)
    @original_month = month
    @original_day = day

    # Christmas comes early!
    super(year, 12, 25)
  end

end

mdt = MyDate.new(2012, 1, 28)

puts mdt.to_s

Remark: @original_month and @original_day are not available in this solution. The following solution extends Date, so you can access the original month and day. For normal dates, the values will be nil.

require 'date'

class Date 
  attr_accessor :original_month 
  attr_accessor :original_day
end  

class MyDate < Date

  def self.new(year, month, day)

    # Christmas comes early!
    date = super(year, 12, 25)
    date.original_month = month
    date.original_day = day
    date
  end

end

mdt = MyDate.new(2012, 1, 28)

puts mdt.to_s
puts mdt.original_month

But I would recommend:

require 'date'

class MyDate < Date

  def self.create(year, month, day)
    @original_month = month
    @original_day = day

    # Christmas comes early!
    new(year, 12, 25)
  end

end

mdt = MyDate.create(2012, 1, 28)

puts mdt.to_s

or

require 'date'

class Date

  def this_year_christmas
    # Christmas comes early!
    self.class.new(year, 12, 28)
  end

end

mdt = Date.new(2012, 1, 28).this_year_christmas

puts mdt.to_s
like image 184
knut Avatar answered Sep 20 '22 07:09

knut