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 using123.seconds
(replace123
with the integer from the database). Useinspect
on the resultingDuration
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 defineduration=(new_duration)
andduration
, which internally callread_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
Rails - How to Add Contacts to Sendgrid Marketing Campaigns via API
My Classes Can't Use Shoes Methods Like Para
Check If String Is Repetition of an Unknown Substring
Ruby: How to Process a CSV File with "Bad Commas"
Differencebetween Def Func(Var) and Def Func=(Var)
Can Ruby Tell If It Is Called from an Interactive Shell or Cron
How to Display Error Messages in a Multi-Model Form with Transaction
How to Display a Link to Individual Microposts? (Ruby on Rails 3)
Assets Precompiling Error with Jquery UI Plugin
How to Sign Out in a Rails App, Using Devise Gem, No Route Matches /Users/Sign_Out
Ruby Ternary Operator and Method Call
How to Collect Real-Time Tweets