Rails 5和PostgreSQL的'Interval'数据类型

8
Rails真的不能很好地支持PostgreSQL的间隔数据类型吗?
我不得不使用2013年这个Stack Overflow答案来创建一个间隔列,现在看起来我需要使用2013年这段代码来让ActiveRecord将间隔视为除字符串以外的其他东西。
是这样吗?我最好使用整数数据类型来表示分钟数吗?

2
Rails源代码似乎在适当的位置定义模式中提到了“interval”,这表明Rails5确实支持该功能。我很想回答,但我没有Rails5的安装程序来验证我的猜测,并且我不确定支持有多广泛(如果真的存在的话)。 - mu is too short
谢谢。看起来一些“interval”相关的东西是最近添加的,所以我将升级到5.1.1并查看是否有更好的效果。 - Vorpulus Lyphane
2
看起来升级解决了第一个问题(允许您轻松创建间隔列),但并未解决第二个问题(使activerecord将列解释为间隔而不是字符串)。我认为使用整数数据类型可能更容易。感谢您的帮助。 - Vorpulus Lyphane
4个回答

4

从Rails 5.1开始,您可以使用Postgres 'Interval'数据类型,因此您可以在迁移中执行以下操作:

add_column :your_table, :new_column, :interval, default: "2 weeks"

虽然 ActiveRecord 只将时间间隔视为字符串,但如果您在 PostgreSQL 数据库中设置 IntervalStyleiso_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找到。

1
有用的提示:将数据库名称插入字符串以实现可移植性:execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'iso_8601'" - Nathan Wallace

2

我曾遇到类似问题,最终采用在ActiveRecord模型中为特定列定义reader方法的方式解决。如下所示:

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 的时间间隔数据类型。

1
这不具有可扩展性,涉及隐式解析、格式化,然后再进行显式解析。 - mlt


0

与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在他的回答中提到的那样设置了你的数据库。不知道为什么有时从Postgres返回的格式是PT42S,有时是00:00:42.000格式 :/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接