Remove/deactivate Ruby/Rails 隐式转换

Remove/deactivate Ruby/Rails implicit conversions

有没有办法 remove/deactivate/monkey 修补 Ruby/Rails 中的隐式转换?

我厌倦了像这样的代码产生的错误:

t = Time.now
t + 3600 == t + 3600.seconds

但是

dt = DateTime.now 
dt + 3600 == dt + 3600.days #(why it's days here and not seconds as with Time ?)

根据加法(或减法)中的日期类型,结果是不同的,因为 Integer 在 Time 的情况下隐式转换为几秒,在 DateTime 的情况下隐式转换为天数。

编辑:

好的。我在这里有一些很好的答案。
也许 "correct" 这种不太一致的 Ruby 行为的更好方法是在有人尝试将 Integer/Fixnum 添加到 Date/Time 时引发异常。 只应接受持续时间,您不这么认为吗?

有办法吗?

警告: Monkey 修补核心 Ruby 功能可能很危险,尤其是在这种情况下,因为许多开发人员都期望 TimeDate 对象在使用 +Fixnum 时有。如果您使用 没有依赖于此预期行为的已知库 来推出自己的解决方案,则可以使用此答案。否则,您将进入一个由不准确的对象或随机不需要的异常引起的未知边缘情况造成伤害的世界。

这实际上是 Date 对象的行为与核心 Ruby 库的 Time 对象的行为。 A DateTime object is an extension of Date, and Rails just extends it a bit further.

这里是 method reference for Date#+,其中指出:

Returns a date object pointing other days after self. The other should be a numeric value. If the other is flonum, assumes its precision is at most nanosecond.

method reference for Time#+ 的行为不同:

Adds some number of seconds (possibly fractional) to time and returns that value as a new Time object.

这两个行为都是 Ruby 用 C 编写的核心和库方法,但是可以在 Ruby 中对这个核心行为进行猴子修补。例如,猴子补丁 DateTime 在添加 Fixnum 时以秒为单位:

class DateTime
  def +(num)
    num = num.to_f / (3600*24) if num.class == Fixnum
    super(num)
  end
end

演示:

vagrant@ubuntu-14:/vagrant$ irb
2.1.2 :001 > require 'date'
 => true 
2.1.2 :002 > class DateTime
2.1.2 :003?>   def +(num)
2.1.2 :004?>     num = num.to_f / (3600*24) if num.class == Fixnum
2.1.2 :005?>     super(num)
2.1.2 :006?>     end
2.1.2 :007?>   end
 => :+ 
2.1.2 :008 > test = DateTime.now
 => #<DateTime: 2015-11-25T12:09:18-05:00 ((2457352j,61758s,869355861n),-18000s,2299161j)> 
2.1.2 :009 > test + 1
 => #<DateTime: 2015-11-25T12:09:19-05:00 ((2457352j,61759s,869355861n),-18000s,2299161j)> 

要解决您的编辑问题,对于您想要的内容,方法是相同的。如果检测到的参数是 Fixnum:

,只需覆盖 类 并引发异常
class Time
  def +(num)
    raise TypeError.new("No implicit conversion of Fixnum into Time") if num.class == Fixnum
    super(num)
  end
end

class Date
  def +(num)
    raise TypeError.new("No implicit conversion of Fixnum into Date") if num.class == Fixnum
    super(num)
  end
end

class DateTime
  def +(num)
    raise TypeError.new("No implicit conversion of Fixnum into DateTime") if num.class == Fixnum
    super(num)
  end
end

要使 Date/DateTime 类 在 rails 中添加秒而不是天:

class Date
  def plus_with_duration other
    if ActiveSupport::Duration === other
      other.since self
    else
      plus_without_duration(other.to_f / (24 * 60 * 60).to_f)
    end
  end
  alias_method :+, :plus_with_duration
end

寻找猴子补丁的方法:

DateTime.instance_method(:+).source_location