Search code examples
ruby-on-railsrubyactivesupport

Overriding an active support method with a ruby refinement


I'd like to use a Ruby Refinement to monkey patch an ActiveSupport method for outputting time in a specific format.

My ultimate goal is to have JSON.pretty_generate(active_record.as_json) print all timestamps in UTC, iso8601, 6 decimals. And I want to have all other timestamp printing behave normally.

This is what I have so far:

module ActiveSupportExtensions
  refine ActiveSupport::TimeWithZone do
    def to_s(_format = :default)
      utc.iso8601(6)
    end
  end
end

class Export
  using ActiveSupportExtensions
  def export
    puts JSON.pretty_generate(User.last.as_json(only: [:created_at]))
  end
end

Export.new.export

Which outputs the following (not what I want).

{
  "created_at": "2022-04-05 14:36:07 -0700"
}

What's interesting, is if I monkey patch this the regular way:

class ActiveSupport::TimeWithZone
  def to_s
    utc.iso8601(6)
  end
end

puts JSON.pretty_generate(User.last.as_json(only: [:created_at]))

I get exactly what I want:

{
  "created_at": "2022-04-05T21:36:07.878101Z"
}

The only issue is that this overrides the entire applications TimeWithZone class, which is not something I want to do for obvious reasons.


Solution

  • Thanks to Lam Phan comment, it's not possible via a refinement unfortunately.

    However I was able to do it by override the default timestamp format.

    # save previous default value
    previous_default = ::Time::DATE_FORMATS[:default]
    
    # set new default to be the utc timezone with iso8601 format
    ::Time::DATE_FORMATS[:default] = proc { |time| time.utc.iso8601(6) }
    
    puts JSON.pretty_generate(User.last.as_json(only: [:created_at]))
    
    # set the default back if we have one
    if previous_default.blank?
      ::Time::DATE_FORMATS.delete(:default)
    else
      ::Time::DATE_FORMATS[:default] = previous_default
    end