Sobre BigDecimal y columnas decimal


#1

Las columnas decimal(x,y) en Rails 1.2 se mapean a objetos BigDecimal.
Me parece perfecto, pero hay algo que no me gusta.

Por ejemplo, una columna:

precio decimal(5,2)

de una tabla “articulos” se mapea en un objeto BigDecimal, pero ese
objeto no “recuerda” la precisión ni la escala de la columna
correspondiente.

Por tanto,

articulo = Articulos.find(1)
articulo.precio # este objeto es BigDecimal, pero no se sabe cuántos
decimales debería contener

Lo que significa que “articulo.precio.to_s” no “dibujará” los decimales
que corresponden según la escala de la columna.

Por ejemplo, si en la columna guardamos el valor 5.20, la expresión
“articulo.precio.to_s” devolverá “5.2”.

De la misma forma, si guardamos el valor “5”, la expresión
“articulo.precio.to_s” devolverá “5.0”.

Si el objeto BigDecimal recordara cuántos decimales debe mostrar,
entonces el método to_s podría dar como resultado siempre valores más
apropiados. En el caso del 5.20, devolvería “5.20”, y en el caso del 5,
devolvería “5.00”. Igualmente, si la columna fuese decimal(5), al
guardar los valores 5.20 y 5, el to_s devolvería siempre “5”.

Así además se podría resolver fácilmente el problema que comento en
http://www.ruby-forum.com/topic/95287

¿Cómo se podría implementar ese comportamiento? Soy bastante novato en
Rails, y aunque he buceado por sus ficheros, todavía no he dado con la
tecla.

¿Alguien me ayuda?


#2

Hola Ricardo

En lugar de dejar que sea .to_s el que pinte el valor has de usar
sprintf para darle el formato exacto que tu quieres. Si lo quieres
siempre con dos decimales sería algo así (te lo digo de memoria, igual
no es exacto, pero va por ahí)

sprintf("%0.02f", articulo.precio)

Para lo que comentas en el post de ruby-forum te sirve lo mismo, de
todas formas allí si las columnas numéricas tienen 0 decimales, lo mejor
es que las definas como integer en lugar de numeric.

Saludos

Ricardo Pérez escribió:

“articulo.precio.to_s” devolverá “5.2”.
Así además se podría resolver fácilmente el problema que comento en
http://www.ruby-forum.com/topic/95287

¿Cómo se podría implementar ese comportamiento? Soy bastante novato en
Rails, y aunque he buceado por sus ficheros, todavía no he dado con la
tecla.

¿Alguien me ayuda?


Andrés Cirugeda E.
ASPgems
Email: andres at aspgems dot com

‘All we have to decide is what to do with the time that is given to us’.
Gandalf.


#3

La verdad es que creo que no hay muchas variantes para esto, mas que
modificar el scaffold generado, convirtiendo el decimal a numerico.

2007/1/26, Ricardo Pérez removed_email_address@domain.invalid:


#4

Disculpen, alguien sabe como adjuntar un pdf a un mail?? Gracias !


Windows Live Spaces en Prodigy/MSN Spaces: Crea tu propio espacio.
http://spaces.live.com


#5

Hola de nuevo, bueno creo que asi fue la solución, pero tengo el problema q
por ejemplo yahoo, lo recibo como .bin, alguien sabe como se hace esto?

attachment :content_type =>‘aplpication/pdf’,
:body => File.read(“prueba.pdf”)

Windows Live Spaces en Prodigy/MSN Spaces: Crea tu propio espacio.
http://spaces.live.com


Ror-es mailing list
removed_email_address@domain.invalid
http://lists.simplelogica.net/mailman/listinfo/ror-es


Live Search en Prodigy/MSN. Todo lo que buscas ahora más rapido
http://live.com


#6

no te estaras equivocando en el tipo?

aplpication/pdf

y debe ser…

application/pdf

? o solo es de
transcripción?On 1/26/07, salvador zalapa removed_email_address@domain.invalid wrote:

To: removed_email_address@domain.invalid
Ror-es mailing list
http://lists.simplelogica.net/mailman/listinfo/ror-es


Jairo Enrique Serrano Castañeda
Estudiante de Maestría en Software Libre
Ingeniero de Sistemas UTB
T - http://www.jsnat.com - http://savio.unitecnologica.edu.co
C - http://www.drupal.org.es - http://www.champetux.org


#7

Gracias por vuestras respuestas.

En realidad el problema no está en el scaffold, sino más adentro: en el
propio BigDecimal. Mi opinión es que un BigDecimal debería tener un
campo de escala para saber cómo imprimirse correctamente cuando le hagas
un to_s.

He hecho algunas modificaciones en el código base de Rails, y al parecer
he dado con la tecla. Si queréis probarlo vosotros, a continuación os
indico los cambios que hay que hacer:

  • en
    /activesupport/lib/active_support/core_ext/bigdecimal/formatting.rb,
    sustituir el contenido por el siguiente:

class BigDecimal #:nodoc:

alias :_original_to_s :to_s

attr_accessor :scale
@scale = 0

def to_s(format=“F”)
s = _original_to_s(format)
a = s.split(".")
if scale and scale != 0
a[0] + “.” + a[1] + “0” * (scale - a[1].length)
elsif scale and scale == 0
a[0] + “.0”
else
a[0]
end
end
end

  • en /activerecord/lib/active_record/base.rb, cambiar la definición del
    método read_attribute_before_type_cast por el siguiente:

def read_attribute_before_type_cast(attr_name)
r = @attributes[attr_name]
column = column_for_attribute(attr_name.to_s)
if column.type == :decimal and r.respond_to?(:scale=)
r.scale = column.scale
end
r
end

  • en
    /activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb,
    cambiar la definición de los métodos type_cast, type_cast_code y
    self.value_to_decimal por los siguientes:

def type_cast(value)
return nil if value.nil?
case type
when :string then value
when :text then value
when :integer then value.to_i rescue value ? 1 : 0
when :float then value.to_f
when :decimal then self.class.value_to_decimal(value, scale)
when :datetime then self.class.string_to_time(value)
when :timestamp then self.class.string_to_time(value)
when :time then self.class.string_to_dummy_time(value)
when :date then self.class.string_to_date(value)
when :binary then self.class.binary_to_string(value)
when :boolean then self.class.value_to_boolean(value)
else value
end
end

def type_cast_code(var_name)
case type
when :string then nil
when :text then nil
when :integer then “(#{var_name}.to_i rescue #{var_name} ? 1 : 0)”
when :float then “#{var_name}.to_f”
when :decimal then
“#{self.class.name}.value_to_decimal(#{var_name}, #{scale})”
when :datetime then
“#{self.class.name}.string_to_time(#{var_name})”
when :timestamp then
“#{self.class.name}.string_to_time(#{var_name})”
when :time then
“#{self.class.name}.string_to_dummy_time(#{var_name})”
when :date then
“#{self.class.name}.string_to_date(#{var_name})”
when :binary then
“#{self.class.name}.binary_to_string(#{var_name})”
when :boolean then
“#{self.class.name}.value_to_boolean(#{var_name})”
else nil
end
end

def self.value_to_decimal(value, scale=nil)
if value.is_a?(BigDecimal)
r = value
elsif value.respond_to?(:to_d)
r = value.to_d
else
r = value.to_s.to_d
end
r.scale = scale
r
end

Todos estos cambios consiguen que un BigDecimal proveniente de una
columna decimal(x,y) contenga dentro un atributo llamado “scale” con el
valor “y”. Y cuando se le pida que se imprima con el to_s, éste tiene en
cuenta el valor de escala.

Lo he probado con el scaffold y con el script/console, y parece que
funciona decentemente (a falta de hacer más pruebas). ¿Qué os parece?