Rails真的不能正确支持PostgreSQL的间隔数据类型吗?
我不得不使用 2013 年的这个 Stack Overflow 答案来创建间隔列,现在看起来我需要使用 2013 年的这段代码来让 ActiveRecord 将间隔视为字符串以外的内容。
是这样吗? 我最好只使用整数数据类型来表示分钟数吗?
从 Rails 5.1 开始,您可以使用 postgres 'Interval' 数据类型,因此您可以在迁移中执行以下操作:
add_column :your_table, :new_column, :interval, default: "2 weeks"
虽然 ActiveRecord 只将间隔视为字符串,但如果在 postgresql 数据库中将IntervalStyle
设置为 iso_8601
,它将以 iso8601 样式显示间隔:2 weeks => P14D
execute "ALTER DATABASE your_database SET IntervalStyle = 'iso_8601'"
然后,可以直接将列解析为ActiveSupport::Duration
在你的model.rb
def new_column
ActiveSupport::Duration.parse self[:new_column]
end
有关ISO8601间隔的更多信息,请访问 https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
我遇到了类似的问题,并为ActiveRecord模型上的特定列定义了读取器方法。喜欢这个:
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
这允许在其他地方使用ActiveSupport::Duration
实例。希望Rails将在不久的将来开始自动处理PostgreSQL间隔数据类型。
Rails 6.1 中提供了更完整和集成的解决方案
<小时 />当前的答案建议覆盖模型中的读者和作者。我接受了alter database
的建议,并在ISO8601间隔内建造了一个宝石,ar_interval
.
它为您提供了一个简单的ActiveRecord::Type
来处理ISO8601字符串的序列化和转换!
测试包括如何使用它的示例。
如果有兴趣,Sam Soffes演示的其他格式可以包含在测试中
与Madis的解决方案类似,这个解决方案处理几分之一秒和ISO8601持续时间:
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
这假设你像Leo在他的答案中提到的那样设置你的数据库。不知道为什么有时它们以PT42S
格式从Postgres返回,有时以00:00:42.000
格式返回:/