Hola,
Seguramente el siguiente problema se habrá discutido en la lista, pues
debe ser bastante
común.
Mi problema es que en un modelo tengo varios campos de tipo decimal
(precision 12, escala 2, para ser más exactos) y querría permitir que
desde formularios para ese modelo se pudieran ingresar valores usando
tanto "." como "," ("1.2" seria lo mismo que "1,2"). Osea usar o bien
coma como separador decimal, o bien punto.
He estado googleando en busca de una solución y no he encontrado nada.
He estado probando a usar el callback before_validation para convertir
las comas en puntos y es cuando he caido en la cuenta de que cuando la
cadena que llega del formulario se convierte en BigDecimal los digitos
despues de la coma se descartan y se queda con el valor entero.
Entonces lo que se me ocurre es en el método update del controlador
correspondiente comprobar todos las cadenas para esos campos, pero son
unos 10, y aún así si luego los campos cambian (se quitan o se añaden)
debería modificar ese metódo para usar los nuevos campos.
En fin, alguien conoce alguna solución más o menos "elegante" ?
Saludos y gracias de antemano
on 09.08.2008 12:37
on 09.08.2008 13:18
2008/8/9 Alvaro Bautista <alvarobp@gmail.com>: > Seguramente el siguiente problema se habrá discutido en la lista, pues > debe ser bastante común. > > Mi problema es que en un modelo tengo varios campos de tipo decimal > (precision 12, escala 2, para ser más exactos) y querrÃa permitir que > desde formularios para ese modelo se pudieran ingresar valores usando > tanto "." como "," ("1.2" seria lo mismo que "1,2"). Osea usar o bien > coma como separador decimal, o bien punto. Para conseguir esto nosotros (ASPgems) hacemos lo siguiente. Definimos def l10n_decimal(*syms) syms.each do |s| class_eval <<-EOS before_save do |record| if record.#{s}_before_type_cast.is_a?(String) record.#{s} = MyAppUtils.parse_decimal(record.#{s}_before_type_cast) end end EOS end end en un initializer, de manera que las clases declaran class Book < AR::Base l10n_decimal :price end parse_decimal son unas heuristicas definidas por nosotros que de un numero en una cadena te sacan algo como sea, no peta nunca, va debajo. Hay dos gotchas de las heuristicas que aceptamos como trade-off: * No se puede entrar "1.200" como mil doscientos, porque nuestra heuristica asume que si hay un solo separador este es decimal. * La aplicacion en particular no parsea "1.200" como mil doscientos a pesar de que es como lo escribe en las vistas en castellano. Como en general la gente no escribe separador de miles a la practica va bien, tambien puedes asumir que solo van a haber dos decimales, y en ese caso modificar la heuristica para que si hay 3 interpretes que el separador es de miles (entonces el trade-off es que "1.20" es decimal pero "1.200" no). En fin eso ya lo decide uno mismo. -- fxn # Returns a BigDecimal out of the string n, 0.0.to_d on failure. def self.parse_decimal(n) return 0.0.to_d if n.blank? n = n.dup # remove everything that cannot be part of a number, as currency symbols or garbage n.gsub!(/[^.,\d]+$/, '') ndots = n.count('.') ncommas = n.count(',') return n.to_d if ndots.zero? && ncommas.zero? # if it has a single separator and it is repeated assume it is a thousands separator if (ndots.zero? && ncommas > 1) || (ndots > 1 && ncommas.zero?) n.tr!('.,', '') return n.to_d end # if n has no comma and at most one dot delegate and return return n.to_d if ncommas.zero? # if it has a comma, but no dot, assume it is a decimal separator return n.sub(',', '.').to_d if ndots.zero? # if we get here it has both a comma and a dot, strip whitespace n = n.strip # take sign and delete it, if any s = n.first == "-" ? -1 : 1 n.sub!(/^[-+]/, '') # extract and remove the decimal part, which is assumed to be the one # after the rightmost separator, no matter whether it is a comma or a dot n.sub!(/[.,](\d*)$/, '') decimal_part = $1 # perhaps the empty string, no problem # in what remains, which is taken as the integer part, any non-digit is # simply ignored n.gsub!(/\D/, '') # done return s*("#{n}.#{decimal_part}".to_d) end
on 09.08.2008 14:15
Muchas gracias Xavier! He implementado tu solución con un par de cambios: En lugar de usar el callback before_save en el metodo l10n_decimal utilizo before_validation. Así cambiando esta línea: n.gsub!(/[^.,\d]+$/, '') por esta return if n =~ /[^.,\d]/ del parse_decimal, puedo comprobar que es un número con validates_numericality_of. Pero todo porque yo lo quiero así. Tu solución me ha venido perfecta. De nuevo, muchas gracias.
on 09.08.2008 14:33
2008/8/9 Alvaro Bautista <alvarobp@gmail.com>: > En lugar de usar el callback before_save en el metodo l10n_decimal > utilizo before_validation. Asà cambiando esta lÃnea: > > n.gsub!(/[^.,\d]+$/, '') > > por esta > > return if n =~ /[^.,\d]/ Molt be! Ojo que ahi no aceptas negativos (igual ya es lo que quieres, pero just in case).