I\'m looking for the best way to use a duration field in a Rails model. I would like the format to be HH:MM:SS (ex: 01:30:23). The database in use is sqlite locally and Postgr
I've wrote a some stub to support and use PostgreSQL's interval
type as ActiveRecord::Duration
.
See this gist (you can use it as initializer in Rails 4.1): https://gist.github.com/Envek/7077bfc36b17233f60ad
Also I've opened pull requests to the Rails there: https://github.com/rails/rails/pull/16919
I tried using ActiveSupport::Duration but had trouble getting the output to be clear.
You may like ruby-duration, an immutable type that represents some amount of time with accuracy in seconds. It has lots of tests and a Mongoid model field type.
I wanted to also easily parse human duration strings so I went with Chronic Duration. Here's an example of adding it to a model that has a time_spent in seconds field.
class Completion < ActiveRecord::Base
belongs_to :task
belongs_to :user
def time_spent_text
ChronicDuration.output time_spent
end
def time_spent_text= text
self.time_spent = ChronicDuration.parse text
logger.debug "time_spent: '#{self.time_spent_text}' for text '#{text}'"
end
end
SUM
query over the duration column to produce a grand total. If you use integers, this is easy and fast.Additionally:
ActiveSupport::Duration
by using 123.seconds
(replace 123
with the integer from the database). Use inspect
on the resulting Duration
for nice formatting. (It is not perfect. You may want to write something yourself.)ActiveSupport::Duration
objects, rather than integers. Simply define duration=(new_duration)
and duration
, which internally call read_attribute
/ write_attribute
with integer arguments.In Rails 5, you can use ActiveRecord::Attributes to store ActiveSupport::Durations as ISO8601 strings. The advantage of using ActiveSupport::Duration over integers is that you can use them for date/time calculations right out of the box. You can do things like Time.now + 1.month
and it's always correct.
Here's how:
Add 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)
Migration
create_table :somethings do |t|
t.string :duration
end
Model
class Something < ApplicationRecord
attribute :duration, :duration
end
Usage
something = Something.new
something.duration = 1.year # 1 year
something.duration = nil
something.duration = "P2M3D" # 2 months, 3 days (ISO8601 string)
Time.now + something.duration # calculation is always correct