Monkey Patch Rails的at_with_coercion方法处理毫秒时间戳遇栈溢出问题
Rails/Mongoid适配毫秒级时间戳:修复栈溢出问题及替代方案
一、修复Monkey Patch的栈溢出问题
你原有的Monkey Patch因错误的方法删除顺序和别名逻辑导致无限递归,正确的做法是先保留原生Time.at的引用,再实现增强逻辑:
修改lib/core_extensions/time.rb为:
class Time class << self # 先保存原生at方法的引用 alias_method :original_at, :at def at_with_coercion(*args) return original_at(*args) if args.size != 1 time_or_number = args.first if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime) original_at(time_or_number.to_f).getlocal elsif time_or_number.is_a?(Integer) && time_or_number.to_s.length == 13 # 毫秒转秒后调用原生方法 original_at(time_or_number / 1000.0) else original_at(time_or_number) end end # 将原生at方法替换为增强版 alias_method :at, :at_with_coercion end end
核心逻辑:用alias_method先把原生at存为original_at,避免删除方法导致的引用丢失,后续调用original_at直接触发原生实现,不会引发递归循环。
二、无需Monkey Patch的替代方案
如果不想全局修改Time类,以下几种局部方案更安全、易维护:
方案1:模型自定义Setter方法
直接在WorkOrder模型中针对due_date字段处理毫秒时间戳:
class WorkOrder include Mongoid::Document include Mongoid::Timestamps field :due_date, type: DateTime def due_date=(value) if value.is_a?(Integer) && value.to_s.length == 13 # 转换毫秒时间戳为DateTime后调用原生setter super(Time.at(value / 1000.0).to_datetime) else super(value) end end end
优点:仅影响当前模型的指定字段,不会干扰其他功能。
方案2:控制器参数预处理
在创建/更新工单的控制器动作中,提前转换参数:
def create work_order_params = params.require(:work_order).permit(:name, :due_date, ...) # 仅处理当前请求的due_date参数 if work_order_params[:due_date].is_a?(Integer) && work_order_params[:due_date].to_s.length == 13 work_order_params[:due_date] = Time.at(work_order_params[:due_date] / 1000.0).to_datetime end @work_order = WorkOrder.new(work_order_params) @work_order.save end
优点:影响范围最小,仅针对特定API动作,调试方便。
方案3:自定义Mongoid类型转换器
创建全局生效的Mongoid字段转换器,自动处理毫秒时间戳:
# lib/mongoid/extensions/millisecond_datetime.rb module Mongoid module Extensions module MillisecondDateTime module ClassMethods def demongoize(object) if object.is_a?(Integer) && object.to_s.length == 13 Time.at(object / 1000.0).to_datetime else super(object) end end end end end end # 注册转换器到DateTime类 DateTime.send(:extend, Mongoid::Extensions::MillisecondDateTime::ClassMethods)
然后在初始化器中加载:
# config/initializers/mongoid_custom_types.rb require Rails.root.join('lib', 'mongoid', 'extensions', 'millisecond_datetime.rb')
优点:所有Mongoid的DateTime字段都会自动处理毫秒时间戳,适合全局需求场景。
内容的提问来源于stack exchange,提问作者S.Mueller
相关产品推荐
相关产品推荐

