You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 21:21:02