Is is possible to use the new normalizes
in Rails 7.1 to convert types before it is assigned. For example,
create_table "procedures" do |t|
. . .
t.float "duration"
. . .
end
class Procedure < ActiveRecord
normalizes :duration, with: -> (d) {
match = /^(?:(\d+)h)\s?(?:(\d+)m$/.match(d.to_s)
if match
match[1].to_i * 60.0 + match[2].to_i
else
d.to_f
end
}
expect(Procedure.new(duration: "3h 30m").duration).to eq(210.0) # Fail: duration == 3
I'm either confused about the intended use of normalizes
or, I'm using it correctly but I have a bug somewhere.
Other than normalization, there are a few ways you can do it:
Simply override duration=
method, if you only need to convert it one way:
class Procedure < ApplicationRecord
def duration=(value)
self[:duration] = if /^(?:(\d+)h)\s?(?:(\d+)m)$/ =~ value.to_s
$1.to_i * 60 + $2.to_i
else
value
end
end
end
or make a custom attribute
type:
# config/initializers/types.rb
class DurationType < ActiveRecord::Type::Float
def cast(value)
if /^(?:(\d+)h)\s?(?:(\d+)m)$/ =~ value.to_s
value = $1.to_i * 60 + $2.to_i
end
super
end
end
ActiveRecord::Type.register(:duration, DurationType)
class Procedure < ApplicationRecord
attribute :duration, :duration
end
You could serialize
if you want to save a float into the database but show a string representation:
class Procedure < ApplicationRecord
class Duration
def self.load(value)
return unless value
"%dh %dm" % value.divmod(60)
end
def self.dump(value)
if /^(?:(\d+)h)\s?(?:(\d+)m)$/ =~ value.to_s
$1.to_i * 60.0 + $2.to_i
else
value
end
end
end
serialize :duration, coder: Duration
end
>> Procedure.create!(duration: "3h15m")
TRANSACTION (0.1ms) begin transaction
Procedure Create (0.6ms) INSERT INTO "procedures" ("duration") VALUES (?) RETURNING "id" [["duration", 195.0]]
# -> ----------------------------------------------------------------------------------------------------^^^^^
TRANSACTION (0.1ms) commit transaction
=> #<Procedure:0x00007fb6c009bfb8 id: 1, duration: "3h 15m">