Does Rails really not properly support PostgreSQL's interval data type?
I had to use this Stack Overflow answer from 2013 to create an interval column, and now it looks like I'll need to use this piece of code from 2013 to get ActiveRecord to treat the interval as something other than a string.
Is that how it is? Am I better off just using an integer data type to represent the number of minutes instead?
From Rails 5.1, you can use postgres 'Interval' Data Type, so you can do things like this in a migration:
add_column :your_table, :new_column, :interval, default: "2 weeks"
Although ActiveRecord only treat interval as string, but if you set the IntervalStyle
to iso_8601
in your postgresql database, it will display the interval in iso8601 style: 2 weeks => P14D
execute "ALTER DATABASE your_database SET IntervalStyle = 'iso_8601'"
You can then directly parse the column to a ActiveSupport::Duration
In your model.rb
def new_column
ActiveSupport::Duration.parse self[:new_column]
end
More infomation of ISO8601 intervals can be find at https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
I had a similar issue and went with defining reader method for the particular column on the ActiveRecord model. Like this:
class DivingResults < ActiveRecord::Base
# This overrides the same method for db column, generated by rails
def underwater_duration
interval_from_db = super
time_parts = interval_from_db.split(':')
if time_parts.size > 1 # Handle formats like 17:04:41.478432
units = %i(hours minutes seconds)
in_seconds = time_parts
.map.with_index { |t,i| t.to_i.public_send(units[i]) }
.reduce(&:+) # Turn each part to seconds and then sum
ActiveSupport::Duration.build in_seconds
else # Handle formats in seconds
ActiveSupport::Duration.build(interval_from_db.to_i)
end
end
end
This allows to use ActiveSupport::Duration
instance elsewhere. Hopefully Rails will start handling the PostgreSQL interval data type automatically in near future.
A more complete and integrated solution is available in Rails 6.1
The current answers suggest overriding readers and writers in the models. I took the alter database
suggestion and built a gem for ISO8601 intervals, ar_interval
.
It provides a simple ActiveRecord::Type
that deals with the serialization and casting of ISO8601 strings for you!
The tests include examples for how to use it .
If there is interest, the additional formats Sam Soffes demonstrates could be included in the tests
Similar to Madis' solution, this one handles fractions of a second and ISO8601 durations:
def duration
return nil unless (value = super)
# Handle ISO8601 duration
return ActiveSupport::Duration.parse(value) if value.start_with?('P')
time_parts = value.split(':')
if time_parts.size > 1
# Handle formats like 17:04:41.478432
units = %i[hours minutes seconds]
in_seconds = time_parts.map.with_index { |t, i| t.to_f.public_send(units[i]) }.reduce(&:+)
ActiveSupport::Duration.build in_seconds
else
# Handle formats in seconds
ActiveSupport::Duration.build(value)
end
end
def duration=(value)
unless value.is_a?(String)
value = ActiveSupport::Duration.build(value).iso8601
end
self[:duration] = value
end
This assumes you setup your database like Leo mentions in his answer. No idea why sometimes they come back from Postgres in the PT42S
format and sometimes in the 00:00:42.000
format :/
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.