Correct use of before_validation

37 views
Skip to first unread message

Joel Pearson

unread,
Jun 25, 2013, 7:26:05 AM6/25/13
to rubyonra...@googlegroups.com
I'm using Rails 3.2.13 with Ruby 2.0.0

I have a field in a form called "hours" which normally passes a
numerical value.

However, my users also want the option to pass time values, like
"01:15", "00:25", ":05", etc.

I've tried to allow for this sort of thing with the below code:

______
validates :hours, :presence => true, :numericality => { :greater_than
=> 0, :less_than => 12 }

before_validation do
STDOUT << 'Test'
self.hours = hours.scan( /(\d*):(\d*)/ ).map { |hrs, mins| hrs.to_f
+ ( mins.to_f / 60.0 ) }.first.round(4) if hours && hours =~ /:/
end
______

The problem I'm getting is that the validation doesn't appear to fire at
all.

When I input ":05", for example.

The form states that Hours is not a number.
I don't see "Test" in the server console.

The code converting from a time string into a float appears to work in
IRB.

--
Posted via http://www.ruby-forum.com/.

Joel Pearson

unread,
Jun 25, 2013, 7:48:07 AM6/25/13
to rubyonra...@googlegroups.com
I've managed to get the event to fire now, but what I get from STDOUT is
0.0
So I guess that means I'm not receiving a string from input.
____

before_validation :convert_hours

protected

def convert_hours
STDOUT << hours
if hours && hours =~ /:/
hrs, mins = hours.scan( /(\d*):(\d*)/ ).first
self.hours = ( hrs.to_f + ( mins.to_f / 60.0 ) ).round(4)
end
end

____

However, the validation that triggers:

____
validates :hours, :presence => true, :numericality => { :greater_than =>
0, :less_than => 12 }
____

States that Hours is not a number.

So where is this 0.0 coming from? Is it even possible to get this value
as a string if it's a Decimal in the database?

Joel Pearson

unread,
Jun 25, 2013, 8:15:06 AM6/25/13
to rubyonra...@googlegroups.com
Catching that 0.0 value was the right direction to look in:

def convert_hours
if hours_before_type_cast && hours_before_type_cast =~ /:/
hrs, mins = hours_before_type_cast.scan( /(\d*):(\d*)/ ).first
self.hours = BigDecimal( ( hrs.to_f + ( mins.to_f / 60.0 ) ), 4 )
end
end

Matt Jones

unread,
Jun 26, 2013, 1:26:56 PM6/26/13
to rubyonra...@googlegroups.com


On Tuesday, 25 June 2013 05:15:06 UTC-7, Ruby-Forum.com User wrote:
Catching that 0.0 value was the right direction to look in:

  def convert_hours
    if hours_before_type_cast && hours_before_type_cast =~ /:/
      hrs, mins = hours_before_type_cast.scan( /(\d*):(\d*)/ ).first
      self.hours = BigDecimal( ( hrs.to_f + ( mins.to_f / 60.0 ) ), 4 )
    end
  end


A cleaner way to do this would be to override the setter for hours:

def hours=(value)
  if value =~ /:/
    converted_value = ... # do the conversion here
    super(converted_value)
  else
    super
  end
end

As an upside, this means that setting hours will *always* return the correct value, even before the before_validation callback runs.

--Matt Jones
end 

Joel Pearson

unread,
Jun 26, 2013, 3:45:47 PM6/26/13
to rubyonra...@googlegroups.com
Thanks for the tip! If the call to "super" triggers the validation then
that should be perfect.
Reply all
Reply to author
Forward
0 new messages