如何使用 Postgres 区间数据类型 Rails 6
How to use Postgres interval data type with Rails 6
根据这里的其他答案,我认为我可以这样做:
rails generate model Availability
class CreateAvailabilities < ActiveRecord::Migration[6.0]
def change
create_table :availabilities do |t|
t.references :user, null: false, foreign_key: true
t.datetime :starts_at, null: false
t.interval :duration, null: false
t.timestamps null: false
end
end
end
Availability.rb
class Availability < ApplicationRecord
attribute :duration, :duration
belongs_to :user
end
config/initializers/duration_type.rb
class DurationType < ActiveRecord::Type::String
def cast(value)
return value if value.blank? || value.is_a?(ActiveSupport::Duration)
ActiveSupport::Duration.parse(value)
end
def serialize(duration)
duration ? duration.iso8601 : nil
end
end
ActiveRecord::Type.register(:duration, DurationType)
rails console
Loading development environment (Rails 6.0.3.2)
> a = Availability.create(user: User.first, starts_at: Time.now, duration: 10.minutes)
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT [["LIMIT", 1]]
(0.2ms) BEGIN
Availability Create (1.7ms) INSERT INTO "availabilities" ("user_id", "starts_at", "duration", "created_at", "updated_at") VALUES (, , , , ) RETURNING "id" [["user_id", 1], ["starts_at", "2020-08-09 18:50:37.732198"], ["duration", "PT10M"], ["created_at", "2020-08-09 18:50:37.747158"], ["updated_at", "2020-08-09 18:50:37.747158"]]
(1.0ms) COMMIT
> a.duration
=> 10 minutes
我以为我在家是自由的,但当我尝试这个时我得到了这个错误
> Availability.first.duration
Availability Load (0.6ms) SELECT "availabilities".* FROM "availabilities" ORDER BY "availabilities"."id" ASC LIMIT [["LIMIT", 1]]
Traceback (most recent call last):
3: from (irb):5
2: from (irb):6:in `rescue in irb_binding'
1: from config/initializers/duration_type.rb:5:in `cast'
ActiveSupport::Duration::ISO8601Parser::ParsingError (Invalid ISO 8601 duration: "00:10:00")
它似乎正确地创建了 Postgres 数据库条目,但我很困惑为什么它无法读回它
可能您需要更改持续时间在数据库中的保存方式。看起来像 00:10:00
这样存储的。但解析器期望字符串采用 ISO8601 格式 (PT10M
)。
class ChangeIntervalStyleInDatabase < ActiveRecord::Migration[6.0]
def self.up
execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'iso_8601'"
end
def self.down
execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'postgres'"
end
end
Rails 6.1+
自从我最初的回答以来,Rails 6.1 已经发布,添加了对 PostgreSQL interval
数据类型的支持。这消除了对转换和序列化值的自定义类型的需要。
根据这里的其他答案,我认为我可以这样做:
rails generate model Availability
class CreateAvailabilities < ActiveRecord::Migration[6.0]
def change
create_table :availabilities do |t|
t.references :user, null: false, foreign_key: true
t.datetime :starts_at, null: false
t.interval :duration, null: false
t.timestamps null: false
end
end
end
Availability.rb
class Availability < ApplicationRecord
attribute :duration, :duration
belongs_to :user
end
config/initializers/duration_type.rb
class DurationType < ActiveRecord::Type::String
def cast(value)
return value if value.blank? || value.is_a?(ActiveSupport::Duration)
ActiveSupport::Duration.parse(value)
end
def serialize(duration)
duration ? duration.iso8601 : nil
end
end
ActiveRecord::Type.register(:duration, DurationType)
rails console
Loading development environment (Rails 6.0.3.2)
> a = Availability.create(user: User.first, starts_at: Time.now, duration: 10.minutes)
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT [["LIMIT", 1]]
(0.2ms) BEGIN
Availability Create (1.7ms) INSERT INTO "availabilities" ("user_id", "starts_at", "duration", "created_at", "updated_at") VALUES (, , , , ) RETURNING "id" [["user_id", 1], ["starts_at", "2020-08-09 18:50:37.732198"], ["duration", "PT10M"], ["created_at", "2020-08-09 18:50:37.747158"], ["updated_at", "2020-08-09 18:50:37.747158"]]
(1.0ms) COMMIT
> a.duration
=> 10 minutes
我以为我在家是自由的,但当我尝试这个时我得到了这个错误
> Availability.first.duration
Availability Load (0.6ms) SELECT "availabilities".* FROM "availabilities" ORDER BY "availabilities"."id" ASC LIMIT [["LIMIT", 1]]
Traceback (most recent call last):
3: from (irb):5
2: from (irb):6:in `rescue in irb_binding'
1: from config/initializers/duration_type.rb:5:in `cast'
ActiveSupport::Duration::ISO8601Parser::ParsingError (Invalid ISO 8601 duration: "00:10:00")
它似乎正确地创建了 Postgres 数据库条目,但我很困惑为什么它无法读回它
可能您需要更改持续时间在数据库中的保存方式。看起来像 00:10:00
这样存储的。但解析器期望字符串采用 ISO8601 格式 (PT10M
)。
class ChangeIntervalStyleInDatabase < ActiveRecord::Migration[6.0]
def self.up
execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'iso_8601'"
end
def self.down
execute "ALTER DATABASE \"#{connection.current_database}\" SET intervalstyle = 'postgres'"
end
end
Rails 6.1+
自从我最初的回答以来,Rails 6.1 已经发布,添加了对 PostgreSQL interval
数据类型的支持。这消除了对转换和序列化值的自定义类型的需要。