Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5 and PostgreSQL 'Interval' data type

Does Rails really not properly support PostgreSQL's interval data type?

I had to use this Stack Overflow answer from 2013 to create an interval column, and now it looks like I'll need to use this piece of code from 2013 to get ActiveRecord to treat the interval as something other than a string.

Is that how it is? Am I better off just using an integer data type to represent the number of minutes instead?

like image 822
Vorpulus Lyphane Avatar asked May 11 '17 16:05

Vorpulus Lyphane


People also ask

Is interval data type in PostgreSQL?

In PostgreSQL the interval data type is used to store and manipulate a time period. It holds 16 bytes of space and ranging from -178, 000, 000 years to 178, 000, 000 years.


3 Answers

From Rails 5.1, you can use postgres 'Interval' Data Type, so you can do things like this in a migration:

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

Although ActiveRecord only treat interval as string, but if you set the IntervalStyle to iso_8601 in your postgresql database, it will display the interval in iso8601 style: 2 weeks => P14D

execute "ALTER DATABASE your_database SET IntervalStyle = 'iso_8601'"

You can then directly parse the column to a ActiveSupport::Duration

In your model.rb

def new_column
  ActiveSupport::Duration.parse self[:new_column]
end

More infomation of ISO8601 intervals can be find at https://en.wikipedia.org/wiki/ISO_8601#Time_intervals

like image 79
Leo Li Avatar answered Oct 07 '22 23:10

Leo Li


I had a similar issue and went with defining reader method for the particular column on the ActiveRecord model. Like this:

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

This allows to use ActiveSupport::Duration instance elsewhere. Hopefully Rails will start handling the PostgreSQL interval data type automatically in near future.

like image 31
Madis Nõmme Avatar answered Oct 07 '22 22:10

Madis Nõmme


A more complete and integrated solution is available in Rails 6.1


The current answers suggest overriding readers and writers in the models. I took the alter database suggestion and built a gem for ISO8601 intervals, ar_interval.

It provides a simple ActiveRecord::Type that deals with the serialization and casting of ISO8601 strings for you!

The tests include examples for how to use it.

If there is interest, the additional formats Sam Soffes demonstrates could be included in the tests

like image 45
Caleb Buxton Avatar answered Oct 07 '22 21:10

Caleb Buxton