Q:

Dealing with Mixed Numbers — In Model?

I have a form with a large number of fields that I am storing in my database as decimals.
However, I'd like the user to be able to input fractions, and specifically mixed numbers (e.g. 38 1/2)
This answer provides a good solution for converting mixed numbers to fractions, and I've modified it and put it in a function in my model:
def to_dec
  self.chest = self.chest.split.map { |r| Rational(r) }.inject(:+).to_f
end

And I've experimented calling it with validate :to_dec and before_save :to_dec. However, neither of these work. 
I know why, but not how to fix it.
By using puts self.chest I'm able to tell that the value is being converted to a number before I have a chance to tweak how that's done. Entering '10 1/2' into my form and saving, puts self.chest gives me 10.0 even if the function is just:
def to_dec
  puts self.chest
end

In addition, puts '38 1/2'.split.map { |r| Rational(r) }.inject(:+).to_d gives me 38.5, so I know the problem is not in the converting.
I've changed the view input type from number to string, but I seem to still end up with a number in my model.
I'm stuck.
Also, some of the other fields also need to support mixed numbers—I'd like to abstract this out somehow so I don't need to write a different function for each value. 

A:

The reason is that ActiveRecord looks at your database schema when it defines your model attributes. This is how all the attributes appear on your model class without you ever defining them manually with attr_accessor :chest (or def chest and def chest=) like you would have to on a plain old Ruby object.
When it does so, ActiveRecord also looks at the column types, and applies type casts when assigning attributes. Hence, because your chest column is a decimal, it converts any string you give it to a float. In doing so it will grab the first bit of the string that can be converted, and discard the rest, which is why you get 38.0.
You can get around this in a couple of ways - you could either just store the numbers as strings in the database, which might save a few conversions (but wouldn't be so useful if you need to use the numeric representation in queries), or you could use a virtual attribute.
Basically, you need to define a getter and a setter like so:
def rational_chest
  # I assume this would create a rational from a float
  # and respond to `to_s` in a sane manner...
  Rational(chest) if chest
end

def rational_chest=(value)
  self.chest = parse_rational(value)
end

private

def parse_rational(value)
  return nil if value.blank?
  value.split.map{|r| Rational(r)}.inject(:+).to_f
end

Then use rational_chest in your form instead of just chest
Since you need to do so on a number of fields, you could resort to a bit of nifty metaprogramming:
def self.rational_attribute(attribute)
  define_method("rational_#{attribute}") do
    Rational(send(attribute))
  end

  define_method("rational_#{attribute}=") do |value|
    send "#{attribute}=", value.split.map{|r| Rational(r)}.inject(:+).to_f
  end
end

def self.rational_attributes(*attributes)
  attributes.each {|attribute| rational_attribute attribute}
end

rational_attributes :chest, :waist, :etc

If you use this in several models, chuck it in a module (without the self.s and extend it!

