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.
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.