Rails 5 和 PostgreSQL 'Interval'数据类型



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格式返回:/

最新更新