Using a Duration Field in a Rails Model

Using a duration field in a Rails model

  • Store as integers in your database (number of seconds, probably).
  • Your entry form will depend on the exact use case. Dropdowns are painful; better to use small text fields for duration in hours + minutes + seconds.
  • Simply run a SUM query over the duration column to produce a grand total. If you use integers, this is easy and fast.

Additionally:

  • Use a helper to display the duration in your views. You can easily convert a duration as integer of seconds to 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.)
  • In your model, you'll probably want attribute readers and writers that return/take ActiveSupport::Duration objects, rather than integers. Simply define duration=(new_duration) and duration, which internally call read_attribute / write_attribute with integer arguments.

How to handle time duration input in rails app

Try this Pull Request in Chronic gem - https://github.com/mojombo/chronic/pull/231

It is able to handle the following duration:

  • one and a half minutes
  • half an hour
  • 1.5 hours
  • & even more

Pasting the code (PR by https://github.com/TalkativeTree) here since it might get deleted:

  class Minutizer

def minutize(text)
text = pre_normalize(text)
extract_time(text)
end

def pre_normalize(text)
text.gsub!(/half an/, 'a half')
text.gsub!(/half a/, 'a half')
text = Numerizer.numerize(text.downcase)
text.gsub!(/an hour/, '1 hour')
text.gsub!(/a hour/, '1 hour')
text.gsub!(/a day/, '1 minute')
text.gsub!(/a minute/, '1 day')
text.gsub!(/a week/, '1 week')
# used to catch halves not covered by Numerizer. Use for the conversion of 'an hour and a half' Previous gsubs will have changed it to '1 hour and haAlf' by this point.
text.gsub!(/(\d+)\s?\w+?\s?and haAlf/) do |m|
m.gsub!(/\A\d+/, ($1.to_f + 0.5).to_s )
end
text.gsub!(/\s?and haAlf/, '')
text.gsub!(/haAlf/, "0.5")
text.gsub!(/(seconds|secnds|second|secnd|secs)/, 'sec')
text.gsub!(/(minutes|minute|mintues|mintes|mintue|minte)/, 'min')
text.gsub!(/(horus|hours|huors|huor|hrs|hr)/, 'hour')
text.gsub!(/(days|day|dy)/, 'day')
text.gsub!(/(weeks|wks|wk)/, 'week')
text
end

def extract_time(text)
minutes = extract(text, 'week')
minutes += extract(text, 'day')
minutes += extract(text, 'hour')
minutes += extract(text, 'min')
minutes += extract(text, 'sec')
end

def extract(text, option)
total = text.match(/(\d+.\d+|\d+)\s?(#{option})/)

return 0 unless total
digitize_time(total[1], option)
end

def digitize_time(time, option)
case option
when 'week'
multiplier = 7 * 24 * 60
when 'day'
multiplier = 24 * 60
when 'hour'
multiplier = 60
when 'min'
multiplier = 1
when 'sec'
multiplier = 1.to_f/60
end

return multiplier * time.to_f
end
end

Just save the above file somewhere and try this below:

You also need 'numerizer' gem installed for minutizer to work.

☁  Documents  irb
2.3.0 :001 > require './minutizer.rb'
=> true
2.3.0 :002 > require 'numerizer'
=> true
2.3.0 :003 > Minutizer.new.minutize('1.5 hours')
=> 90.0
2.3.0 :004 >

How to create a custom validator to reject a time duration 10 minutes

The problem with my custom validator was caused by my failure to include the option to "ignore_date" in the time_select HTML.

While reviewing my source code, I noticed date values were included with a user's inputted time values. The custom validator failed because of these "hidden fields." Ruby on Rails documentation states: "This method [time_select] will also generate 3 input hidden tags, for the actual year, month and day unless the option :ignore_date is set to true."

My custom validator works as expected after I set the option ":ignore_date to true" in my HTML. That change in the HTML solved the problem. Users can no longer save cardio exercises less than 10 minutes in duration. I hope this is useful information to someone else in the future.

I give credit to @max for helping me improve my custom validator, and for explaining why the placement of "self" in the validator would not work versus in the model.

Display duration in a human readable format such as X hours, Y minutes

I would start with something like this:

def duration_of_interval_in_words(interval)
hours, minutes, seconds = interval.split(':').map(&:to_i)

[].tap do |parts|
parts << "#{hours} hour".pluralize(hours) unless hours.zero?
parts << "#{minutes} minute".pluralize(minutes) unless minutes.zero?
parts << "#{seconds} hour".pluralize(seconds) unless seconds.zero?
end.join(', ')
end

duration_of_interval_in_words('02:00:00')
# => '2 hours'

duration_of_interval_in_words('02:01:00')
# => '2 hours, 1 minute'

duration_of_interval_in_words('02:15:00')
# => '2 hours, 15 minutes'

Confusing ActiveSupport::Duration calculation results

ActiveSupport handles months and days (and years) separately from hours, minutes and seconds.

This is because a duration of one hour (or 23 minutes) is always an exact number of seconds. A month on the other hand is a varying number of days and a day can have either 23, 24, or 25 hours depending on daylight savings changes.

It sounds like you don't need this functionality, in which case storing a number of seconds is probably simpler.

Rails event start_time+length=end_time

You don't need a duration, as long as you have the initial and the final date you can easily compute the duration on the fly.

You can have a duration in your model

def duration
end_time - start_time
end

Also, you should not use end as a field name, because it is a reserved keyword in Ruby and it may conflict with the parser in some cases. For example, you won't be able to write

def duration
end - start
end

If instead, you want to create a database column, then simply create an integer duration attribute and you can use either an active record callback to compute the duration when the record is saved.

In Rails, how do I map a millisecond field to hours, minutes, and seconds fields?

As you haven't mentioned your controller, I am assuming the controller as TimersController. You need to make changes in the view as well as timer_params method in the controller.

Here is the code on view:

<div class="field">
<%= f.label "Time" %>
<%= select_tag('timer[hour]', options_for_select((1..12).to_a), {:prompt => 'Select Hour'} ) %> hrs
<%= select_tag('timer[minute]', options_for_select((1..60).to_a), {:prompt => 'Select Minutes'} ) %> min
<%= select_tag('timer[second]', options_for_select((1..60).to_a), {:prompt => 'Select Minutes'} ) %> sec
<%#= f.number_field :time_in_ms %>
</div>

and here is the code on controller:

def create
@timer = Timer.new(timer_params)

respond_to do |format|
if @timer.save
format.html { redirect_to @timer, notice: 'Timer was successfully created.' }
format.json { render :show, status: :created, location: @timer }
else
format.html { render :new }
format.json { render json: @timer.errors, status: :unprocessable_entity }
end
end
end

def timer_params
# Main Code goes here
params.require(:timer).permit(:time_in_ms)
params[:timer][:time_in_ms] = (params[:timer][:hour].to_i * 60 * 60 + params[:timer][:minute].to_i * 60 + params[:timer][:second].to_i) * 1000
end

I have left the validation part. You can implement that on client side as well as on server side.

In Rails/Formtastic how do I restrict hours in the time input field

I don't see any options to limit it through Formtastic. You could just display the input as a select and pass it the options you want explicitly.

<%= f.input :hours, :as=>:select, :collection => (0..4) %>
<%= f.input :minutes, :as=>:select, :collection => [0,15,30,45] %>

Then you'll probably need to add these virtual attributes in the model:

before_save :set_duration

def set_duration
self.duration = @hours * 60 + @minutes
end

def hours
self.duration / 60;
end

def minutes
self.duration % 60;
end

def hours=(h)
@hours = h
end

def minutes=(m)
@minutes = m
end

def duration=(d)
@hours = d / 60
@minutes = d % 60
self.set_duration
end

And you might want to look at this answer to get them to look more like the original.

There might be some clever, quicker way to do this, but this is the first thing that comes to mind.



Related Topics



Leave a reply



Submit