Tratar con números mixtos – ¿En modelo?

Tengo un formulario con una gran cantidad de campos que estoy almacenando en mi base de datos como decimales.

Sin embargo, me gustaría que el usuario pueda ingresar fracciones y específicamente números mixtos (por ejemplo, 38 1/2)

Esta respuesta proporciona una buena solución para convertir números mixtos en fracciones, y la modifiqué y la puse en una función en mi modelo:

def to_dec self.chest = self.chest.split.map { |r| Rational(r) }.inject(:+).to_f end 

Y he experimentado llamándolo con validate :to_dec y before_save :to_dec . Sin embargo, ninguno de estos trabajos.

Sé por qué, pero no cómo arreglarlo.

Mediante el uso de puts self.chest puedo decir que el valor se está convirtiendo en un número antes de tener la oportunidad de modificar cómo se hace. Ingresando ’10 1/2 ‘en mi formulario y guardando, puts self.chest me da 10.0 incluso si la función es simplemente:

 def to_dec puts self.chest end 

Además, puts '38 1/2'.split.map { |r| Rational(r) }.inject(:+).to_d puts '38 1/2'.split.map { |r| Rational(r) }.inject(:+).to_d me da 38.5, así que sé que el problema no está en la conversión.

He cambiado el tipo de entrada de vista de número a cadena, pero parece que todavía tengo un número en mi modelo.

Estoy atascado.

Además, algunos de los otros campos también deben admitir números mixtos; me gustaría abstraerlo de alguna manera, por lo que no necesito escribir una función diferente para cada valor.

La razón es que ActiveRecord mira el esquema de su base de datos cuando define los atributos de su modelo. Así es como aparecen todos los atributos en su clase de modelo sin que usted los haya definido manualmente con attr_accessor :chest (o def chest y def chest= ) como si tuviera que hacerlo en un objeto Ruby viejo y sencillo.

Cuando lo hace, ActiveRecord también examina los tipos de columna y aplica conversiones de tipo al asignar atributos. Por lo tanto, debido a que tu columna de chest es un decimal, convierte cualquier cadena que le des a float. Al hacerlo, tomará el primer bit de la cadena que se puede convertir y descartará el rest, por lo que obtiene 38.0 .

Puede solucionar esto de varias maneras: puede almacenar los números como cadenas en la base de datos, lo que podría ahorrar algunas conversiones (pero no sería tan útil si necesita usar la representación numérica en las consultas), o podrías usar un atributo virtual .

Básicamente, necesitas definir un getter y un setter así:

 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 

Luego use rational_chest en su forma en lugar de solo chest

Ya que necesita hacerlo en varios campos, podría recurrir a un poco de ingeniosa metaprogtwigción:

 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 

Si usa esto en varios modelos, colóquelo en un módulo (sin el self. S y extend !