Formatos numéricos

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

2008/8/9 Alvaro B. [email protected]:

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!(/.,$/, ‘’)
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

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.

2008/8/9 Alvaro B. [email protected]:

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).