1

Based on other answers here I thought I could do this:

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 $1  [["LIMIT", 1]]
   (0.2ms)  BEGIN
  Availability Create (1.7ms)  INSERT INTO "availabilities" ("user_id", "starts_at", "duration", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) 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

I thought I was home free but I get this error when I try this

> Availability.first.duration
  Availability Load (0.6ms)  SELECT "availabilities".* FROM "availabilities" ORDER BY "availabilities"."id" ASC LIMIT $1  [["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")

It seems to be creating the Postgres database entry correctly but I'm confused why it's not able to read it back

1 Answer 1

3

Might be that you need to change how the duration is persisted in the database. It looks like it’s stored like 00:10:00. But the parser is expecting the string to be in ISO8601 format (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+

Since my original answers, Rails 6.1 has been released adding support for PostgreSQL interval datatype. This eliminates the need for a custom type that casts and serializes values.

Sign up to request clarification or add additional context in comments.

1 Comment

that works great, thank you. If you don't want to alter the database for everyone I was also able to do it per db connection as an initializer -- see stackoverflow.com/questions/63346731/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.