【问题标题】:Rails 5 and PostgreSQL 'Interval' data typeRails 5 和 PostgreSQL 'Interval' 数据类型
【发布时间】:2021-02-15 07:45:26
【问题描述】:

Rails 真的不能正确支持 PostgreSQL 的区间数据类型吗?

我必须使用 2013 年的 this Stack Overflow answer 来创建间隔列,现在看来我需要使用 2013 年的 this piece of code 才能让 ActiveRecord 将间隔视为字符串以外的东西。

是这样的吗?我最好只使用整数数据类型来表示分钟数吗?

【问题讨论】:

  • Rails 消息来源似乎在 appropriate places 中提到了 interval,所以它看起来像 Rails5。我会回答,但我没有方便的 Rails5 设置来验证我的猜测,我不确定支持的范围有多大(如果真的有的话)。
  • 谢谢。看起来最近添加了一些“间隔”的东西,所以我要升级到 5.1.1,看看它是否工作得更好。
  • 看起来升级解决了第一个问题(允许您轻松创建间隔列),但没有解决第二个问题(让 activerecord 将列解释为间隔,而不是字符串)。我认为只使用整数数据类型可能更容易。感谢您的帮助。

标签: ruby-on-rails postgresql activerecord


【解决方案1】:

从 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

【讨论】:

  • 实用提示:将数据库名称插入字符串以方便移植:execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'iso_8601'"
【解决方案2】:

我遇到了类似的问题,并为 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 区间数据类型。

【讨论】:

  • 那是不可扩展的,涉及到隐式解析、格式化,然后再显式解析。
【解决方案3】:
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-25
    • 2014-03-25
    • 2010-12-29
    • 2014-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多