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