ruby-on-railsruby-on-rails-7.2

Rails 7.2 - unexpected behaviour with duration and postgres interval type


I'm upgrading a Rails app from 7.1.3.4 to 7.2 and I'm quite stumped by a new behaviour.

We have an object analagous to:

class Event < ApplicationRecord
  before_save :set_duration
  before_save :update_classification

  def set_duration
    self.duration = end_datetime - start_datetime
  end

  def update_classification
    if duration >= 4.hours 
      self.classification = "long"
    else
      self.classification = "short"
    end
  end
end

and tests like:

class EventTest < ActiveSupport::TestCase
  def setup
    @event = build(:event)
  end

  test "duration and classification are updated correctly before_save" do
    @event.start_datetime = Time.now.utc
    @event.end_datetime = 6.hours.from_now.utc

    @event.save!
    assert_equal "Long", @event.classification
    assert_equal 6.hours, @event.duration.round
  end
end

In Rails 7.1.3.4, this passed. Following the 7.2 upgrade, it fails on the last line: NoMethodError: undefined method `round' for nil. Using debugger, I can see that duration and classification are both nil before the save! call, but afterwards only classification has a value. If I call @event.reload after the save, then the duration value loads correctly! In the console, a save call produces SQL like:

Event Create (2.0ms)  INSERT INTO "Events" ("created_at", "updated_at", "start_datetime", "end_datetime", "duration", "classification") VALUES ($1, $2, $3, $4) RETURNING "id"  [["created_at", "2024-10-17 20:51:14.844042"], ["updated_at", "2024-10-17 20:51:14.844042"], ["start_datetime", "2024-10-17 20:50:56.515026"], ["end_datetime", "2024-10-18 02:50:55.377912"], ["classification", "Long"], ["duration", "PT5H59M58.86288599999898S"]]

We're using Postgres and the schema looks something like:

  create_table "impairments", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "start_datetime"
    t.datetime "end_datetime"
    t.string "classification"
    t.interval "duration"
  end

So, what am I missing? I notice in the 7.2 release notes, it says: Remove deprecated support to quote ActiveSupport::Duration. Am I right in guessing it's something to do with this? Is there an obvious or easy way to solve the issue? I can refactor many tests and production code with reload statements, but that's not ideal, and I suspect I'm missing something elementary.


Solution

  • The solution was remarkably easy. Adding .to_i on the set_duration call solved it - apparently passing an int to postgres instead of a float makes all the difference!

      def set_duration
        self.duration = (end_datetime - start_datetime).to_i
      end
    

    It's still not totally clear to me why Rails 7.2 broke the previous functionality, but a quick solution like this is a win.