Aceptar multiples separadores decimales

75 views
Skip to first unread message

Jose Vicente Martinez Mendoza

unread,
Mar 9, 2012, 8:54:23 AM3/9/12
to rub...@googlegroups.com
Hola,

Tengo un formulario donde el usuario puede introducir un importe pero necesito que independientemente del formato de separador de decimales que se utilice ya sea , o . mi modelo lo acepte.

Ahora mismo cuando utilizo un . lo coge perfectamente pero si utilizo una coma me da una excepción

También quiero poder aceptar .29 

La mejor forma es utilizar la internalización?

Mi modelo lo tengo definido asi:

 field :amount, :type => Float
 validates_presence_of :amount
 validates_numericality_of :amount, :greater_than => 0

Gracias

--
Jose

twitter: @jomarmen


Instr. Dwayne Macgowan

unread,
Mar 9, 2012, 9:00:53 AM3/9/12
to rub...@googlegroups.com
Podrías interpretar el dato cono un String y parsearlo vos. Recién entonces los almacenás como Float.

Dwayne Macgowan

Instructor del Método DeRose


Para conocer el Método DeRose visitá:

Blog de DeRose
Entrevista a DeRoseEntrevista a Edgardo Caramella
Demostración de Técnicas

 




2012/3/9 Jose Vicente Martinez Mendoza <joma...@gmail.com>

Geronimo Diaz

unread,
Mar 9, 2012, 9:13:11 AM3/9/12
to rub...@googlegroups.com
El 09/03/12 10:54, Jose Vicente Martinez Mendoza escribió:
Hola,

Tengo un formulario donde el usuario puede introducir un importe pero necesito que independientemente del formato de separador de decimales que se utilice ya sea , o . mi modelo lo acepte.

Ahora mismo cuando utilizo un . lo coge perfectamente pero si utilizo una coma me da una excepción

También quiero poder aceptar .29 

La mejor forma es utilizar la internalización?
la internalizacion te va a servir para mostrar el importe segun el locale, pero el input desde el usuario lo tendras que manipular vos para contemplar este caso, @record.amount = params[:record][:amount].sub(',', '.').to_f quizas te sirva.

saludos


Mi modelo lo tengo definido asi:

 field :amount, :type => Float
 validates_presence_of :amount
 validates_numericality_of :amount, :greater_than => 0

Gracias

--
Jose

twitter: @jomarmen




--
H. Gerónimo Díaz.
RoR Developer

gtalk: geronimod
skype: geronimodiaz
jabber: gd...@jabber.belnet.be

twitter: @geronimodiaz
linkedIn: http://ar.linkedin.com/pub/geronimod
github: http://geronimod.github.com

Michel Martens

unread,
Mar 9, 2012, 9:24:29 AM3/9/12
to rub...@googlegroups.com
Hola,

2012/3/9 Jose Vicente Martinez Mendoza <joma...@gmail.com>:


>
> Tengo un formulario donde el usuario puede introducir un importe pero
> necesito que independientemente del formato de separador de decimales que se
> utilice ya sea , o . mi modelo lo acepte.
>
> Ahora mismo cuando utilizo un . lo coge perfectamente pero si utilizo una
> coma me da una excepción
>
> También quiero poder aceptar .29
>
> La mejor forma es utilizar la internalización?

Lo mejor es que el modelo no tenga la responsabilidad de convertir
distintos valores a la representación que quiere guardar. En el lugar
en donde recibís los datos del formulario deberías tener una función
que convierta el string que envía el usuario en un valor que acepte tu
modelo. Yo usaría algo como Bureaucrat o Scrivener.

https://github.com/tizoc/bureaucrat
https://github.com/soveran/scrivener

> Mi modelo lo tengo definido asi:
>
>  field :amount, :type => Float
>  validates_presence_of :amount
>  validates_numericality_of :amount, :greater_than => 0

Hay que tener mucho cuidado con los floats porque son muy imprecisos.
Si vas a operar con `amount`, entonces te recomiendo que o bien uses
enteros (la parte decimal pasa a ser una formalidad en la
presentación), o bien que uses BigDecimal de la standard library (esto
es lo que estoy haciendo en un proyecto).

Te paso un ejemplo: https://gist.github.com/25263a383fc0bf4b2d0e

Saludos.

Ary Borenszweig

unread,
Mar 9, 2012, 12:22:38 PM3/9/12
to rub...@googlegroups.com
Yo prefiero tener esa lógica en el modelo.

class Model
  def amount=(value)
    value = value.gsub(',', '.') if value.is_a? String
    @amount = value.to_f
  end
end

Así incluso si asigno un valor desde la consola o un script, funciona como corresponde.

Son 4 líneas de código. De la otra manera terminás requiriendo una gema, escribiendo nuevas clases y demás.

KISS :-)

Bruno Deferrari

unread,
Mar 9, 2012, 2:20:08 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Yo prefiero tener esa lógica en el modelo.
>
> class Model
>   def amount=(value)
>     value = value.gsub(',', '.') if value.is_a? String
>     @amount = value.to_f
>   end
> end
>
> Así incluso si asigno un valor desde la consola o un script, funciona como
> corresponde.
>
> Son 4 líneas de código. De la otra manera terminás requiriendo una gema,
> escribiendo nuevas clases y demás.
>
> KISS :-)
>

Ese código tan simple como lo ves, tiene una falla grande, y es que
acepta valores que ni siquiera son válidos.

Lo correcto es usar Float(valor) y no valor.to_f

Dejando de lado eso, las conversiones implícitas, aunque parezcan
convenientes hacen todo más complicado, agregan más complejidad y más
casos borde que usar los tipos correctos donde van.

--
BD

Bruno Deferrari

unread,
Mar 9, 2012, 3:08:48 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Bruno Deferrari <uti...@gmail.com>:

> 2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:
>> Yo prefiero tener esa lógica en el modelo.
>>
>> class Model
>>   def amount=(value)
>>     value = value.gsub(',', '.') if value.is_a? String
>>     @amount = value.to_f
>>   end
>> end
>>
>> Así incluso si asigno un valor desde la consola o un script, funciona como
>> corresponde.
>>
>> Son 4 líneas de código. De la otra manera terminás requiriendo una gema,
>> escribiendo nuevas clases y demás.
>>
>> KISS :-)
>>
>
> Ese código tan simple como lo ves, tiene una falla grande, y es que
> acepta valores que ni siquiera son válidos.
>
> Lo correcto es usar Float(valor) y no valor.to_f
>

Así lo hace Bureaucrat:

def to_object(value)
if Validators.empty_value?(value)
return nil
end

begin
Utils.make_float(value.to_s)
rescue ArgumentError
raise ValidationError.new(error_messages[:invalid])
end
end

y make_float

def make_float(value)
value += '0' if value.is_a?(String) && value != '.' && value[-1,1] == '.'
Float(value)
end

make_float es más complicado de lo que se necesita para Ruby 1.9, la
razón por la cual ese código es así es que cuando se hizo Bureaucrat
la versión de Ruby que se usaba en ese momento era 1.8.6 (por eso el
slice para mirar el ".", y por eso el tener que prefijar un "0" en
algunos casos para que se parsee bien).

En fin, no todo siempre es tan simple/fácil como parece, siempre hay
alguna molestia de costado que no estamos considerando hasta que es
demasiado tarde.

Igual estoy de acuerdo con que agregar una librería entera solo para
manejar un solo caso es overkill. La idea de Bureaucrat o Scrivener no
es resolver un caso puntual, si no cambiar la manera en que se
resuelve el proyecto en su totalidad al desacoplar las partes y
separar las responsabilidades.

--
BD

Michel Martens

unread,
Mar 9, 2012, 3:31:31 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Yo prefiero tener esa lógica en el modelo.
>
> class Model
>   def amount=(value)
>     value = value.gsub(',', '.') if value.is_a? String
>     @amount = value.to_f
>   end
> end
>
> Así incluso si asigno un valor desde la consola o un script, funciona como
> corresponde.

Al margen de lo que dijo Bruno, es raro que desde la consola quieras
hacer foo.amount = "2,442".

Además, la capa en donde se realiza la transformación también la podés
usar desde la consola o desde un script.

> Son 4 líneas de código. De la otra manera terminás requiriendo una gema,
> escribiendo nuevas clases y demás.

La cantidad de líneas de código no tiene nada que ver con determinar
de quién es la responsabilidad de hacer esa transformación. Con tu
argumento, se podría decir que es mejor hacer una aplicación
monolítica sin reutilizar otras herramientas.

>
> KISS :-)

Con esto estás diciendo que tu solución es más simple, pero en
realidad yo veo todo lo contrario. Un modelo con un atributo ahora se
tiene que ocupar de validar lo que manda un usuario desde un
formulario. Ahora ese modelo es "complected". Te recomiendo que veas
todo lo que fuimos linkeando sobre simplicidad, sobre todo Simple vs
Easy: http://www.infoq.com/presentations/Simple-Made-Easy

Ary Borenszweig

unread,
Mar 9, 2012, 4:28:03 PM3/9/12
to rub...@googlegroups.com
Simplemente no me parece que para aceptar floats con coma en una parte de mi código me tenga que instalar una gema, aprender a usarla, declarar un módulo, incluirlo, o como se use.

Qué valores inválidos acepta lo que definí? Es cierto igual que tendría que haber usado Float(), aunque la documentación de ese método es:

Returns arg converted to a float. Numeric types are converted directly, the rest are converted using arg.to_f. As of Ruby 1.8, converting nil generates a TypeError.

Así que básicamente se llama a to_f.

Resulta mucho más fácil de testear también:

it "assigns float with comma" do
  model.amount = "2,5"
  model.amount.should eq(2.5)
end

Matías Flores

unread,
Mar 9, 2012, 4:46:12 PM3/9/12
to rub...@googlegroups.com
El día 9 de marzo de 2012 18:28, Ary Borenszweig
<a...@esperanto.org.ar> escribió:

> Simplemente no me parece que para aceptar floats con coma en una parte de mi
> código me tenga que instalar una gema, aprender a usarla, declarar un
> módulo, incluirlo, o como se use.
>
> Qué valores inválidos acepta lo que definí?

amount = 2.40 # => 2.4
amount = "2.40" # => 2.4
amount = "2,40") # => 2.4
amount = "2000,40") # => 2000.4
amount = "2,000.40") # => 2.0
amount = "$2000.40" # => 0.0

Nicolás Sanguinetti

unread,
Mar 9, 2012, 4:48:48 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Returns arg converted to a float. Numeric types are converted directly, the
> rest are converted using arg.to_f. As of Ruby 1.8, converting nil generates
> a TypeError.
>
> Así que básicamente se llama a to_f.

Excepto por la "pequeña diferencia" de:

irb(main):001:0> nil.to_f
=> 0.0
irb(main):002:0> Float(nil)
TypeError: can't convert nil into Float
from (irb):2:in `Float'
from (irb):2
from /Users/foca/.rbenv/versions/1.9.3-p0-perf/bin/irb:12:in `<main>'

o

irb(main):003:0> "pepe".to_f
=> 0.0
irb(main):004:0> Float("pepe")
ArgumentError: invalid value for Float(): "pepe"
from (irb):4:in `Float'
from (irb):4
from /Users/foca/.rbenv/versions/1.9.3-p0-perf/bin/irb:12:in `<main>'

Es una diferencia importante. Float() es más seguro que #to_f.

-foca

Michel Martens

unread,
Mar 9, 2012, 4:56:51 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Simplemente no me parece que para aceptar floats con coma en una parte de mi
> código me tenga que instalar una gema, aprender a usarla, declarar un
> módulo, incluirlo, o como se use.

Estás mezclando conceptos. Por un lado, está el tema de no delegar en
el modelo la responsabilidad de convertir el valor que manda el
usuario desde un formulario. Por otro lado, está la sugerencia usar
otra herramienta para abstraer ese problema definitivamente. Nadie te
está obligando a instalar una gema, aprender a usarla, etc. Eso es una
sugerencia que podés ignorar tranquilamente, como bien quedó
demostrado.

> Qué valores inválidos acepta lo que definí? Es cierto igual que tendría que
> haber usado Float(), aunque la documentación de ese método es:
>
> Returns arg converted to a float. Numeric types are converted directly, the
> rest are converted using arg.to_f. As of Ruby 1.8, converting nil generates
> a TypeError.
>
> Así que básicamente se llama a to_f.
>
> Resulta mucho más fácil de testear también:
>
> it "assigns float with comma" do
>   model.amount = "2,5"
>   model.amount.should eq(2.5)
> end

Como dice Mencken: "For every problem there is a solution which is
simple, clean and wrong."

Fijate los casos que plantearon Matías y Foca.

Bruno Deferrari

unread,
Mar 9, 2012, 5:31:27 PM3/9/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Simplemente no me parece que para aceptar floats con coma en una parte de mi
> código me tenga que instalar una gema, aprender a usarla, declarar un
> módulo, incluirlo, o como se use.
>
> Qué valores inválidos acepta lo que definí? Es cierto igual que tendría que
> haber usado Float(), aunque la documentación de ese método es:
>
> Returns arg converted to a float. Numeric types are converted directly, the
> rest are converted using arg.to_f. As of Ruby 1.8, converting nil generates
> a TypeError.
>
> Así que básicamente se llama a to_f.
>

Como ya explicó foca, no son iguales. to_f es mucho más permisivo, no
valida el input mientras que Float si.

> Resulta mucho más fácil de testear también:
>
> it "assigns float with comma" do
>   model.amount = "2,5"
>   model.amount.should eq(2.5)
> end
>

Y apesar de que hiciste ese test, no fue suficiente como ya mostró
Matías. El problema, aunque básico, es mucho más complicado de lo que
parece a primera vista.

Ahora, viendo lo complejo que se vuelve algo tan básico como querer
aceptar números con comas, yo quiero que pienses y te imagines la
complejidad que le agrega esto tanto al ORM como tus modelos por
querer hacer todo en el mismo lugar en ves de separar las
responsabilidades en abstracciones separadas según corresponda.

--
BD

Jose Vicente Martinez Mendoza

unread,
Mar 9, 2012, 5:58:40 PM3/9/12
to rub...@googlegroups.com
Después de leer todos vuestros interesantes comentarios con lo que me quedo es que el responsable último,  creo que tiene que ser el modelo. La forma de implementar la solución que parece que no es trivial ya va por gustos, a mi de todo lo que se ha comentado lo mejor es utilizar una gema más que nada para seguir con la idea de "Not Repeat Yourself"  que cueste más que otra solución no significa que sea peor.

Al final la utilización de la gemma no implica tener el conocimiento exacto de lo que hace, sino que se da por supuesto que el funcionamiento es correcto si no para que utilizarla?

De las dos gemas se ha comentado me quedo porque parece más simple con Scrivener, pero no consigo que me funcione correctamente, pero esto ya es otro tema.

Una pregunta mas, soy novato todavía en rails y el método que se ha comentado 

  def amount=(value)
    value = value.gsub(',','.') if value.is_a? String
    @amount = value.to_f
  end

Como funciona exactamente? Se llama al método siempre que se asigna un valor a amount? y lo que devuelve es la variable @amount?
--
Jose

twitter: @jomarmen


Ary Borenszweig

unread,
Mar 9, 2012, 6:01:26 PM3/9/12
to rub...@googlegroups.com
Me pareció que la discusión era cuál es la mejor manera de aceptar strings en varios formatos.

Ustedes responden: usando tal o cual gema. Pero siguen sin dar detalles de cómo implementar ese método.

Yo digo: definé el método "amount=" y gasto 1 segundo de mi tiempo en proveer un ejemplo de implementación. Jamás fue mi intención que ese ejemplo funcione correctamente. Está claro que sólo requiriendo bureaucrat no se van a empezar a aceptar mágicamente los strings en esos formatos.

(espero que nadie esté enojado con esta discución, en los emails se suelen perder las caras y las emociones... paz :-P)

Creo que mientras menos código haya en un programa, más fácil es entenderlo (mientras no esté todo ofuscado). Sin requerir ninguna gema y agregando el método "amount=", les puedo asegurar que la cantidad del código final es mucho menor. Por ende, el proyecto es más entendible.

De todas maneras es una creencia mía. No nos olvidemos del mail de Emmanuel (defocus), no hice ninguna estadística para demostrarlo. :-)

En general, me gusta que los métodos acepten "lo que venga mientras tenga sentido". Pueden ver que eso es cierto en muchas clases core de ruby, y hasta en active record (Model.find(1) == Model.find("1")). Por eso me pareció sensato que amount=(value) acepte los valores en el formato que venga, mientras tenga sentido. Así no hay que repetir la lógica de conversión por todas partes (acordarse de ponerla en tal formulario, en tal otro).

Bruno Deferrari

unread,
Mar 10, 2012, 10:47:03 AM3/10/12
to rub...@googlegroups.com
2012/3/9 Ary Borenszweig <a...@esperanto.org.ar>:

> Me pareció que la discusión era cuál es la mejor manera de aceptar strings
> en varios formatos.
>
> Ustedes responden: usando tal o cual gema. Pero siguen sin dar detalles de
> cómo implementar ese método.
>

La respuesta fue: hacerlo fuera del modelo, para evitar hacer
validaciones y conversiones de input de usuario a nivel de modelo, ya
que no es bueno hacelro ahí. Se nombraron 2 librerías que ya hacen
esto. El consejo no se trataba de las librerías en si, se trataba de
no meter esta lógica en el modelo, nada más ni nada menos.

A esto se le sumó el consejo de usar BigDecimal por los problemas de
presición con los cuales nos podemos encontrar al usar floats.

Ahora, respecto a usar Bureaucrat o Scrivener, vos pensá que no solo
le van a resolver este caso puntual que tiene, si no que van a hacer
algo bueno por la estructura de su programa en general. Así como en
este caso puntual lo que estaba haciendo era esto de los números con
coma, yo me imagino que su programa no es solo esto (un form con un
solo input donde hay que aceptar un número), si no que hay más cosas.
Si ese es el caso, que es lo más probable, de seguro le van a servir
las librerías (sobretodo Scrivener, porque Bureaucrat probablemente
hace bastante más de lo que espera)

> Yo digo: definé el método "amount=" y gasto 1 segundo de mi tiempo en
> proveer un ejemplo de implementación. Jamás fue mi intención que ese ejemplo
> funcione correctamente. Está claro que sólo requiriendo bureaucrat no se van
> a empezar a aceptar mágicamente los strings en esos formatos.
>

La crítica a tu ejemplo, más allá de subestimar el problema y por ende
cometer errores, está en que decidiste acoplar eso al modelo, en ves
de definir un componente externo (un módulo con helpers, una clase
para validar datos, lo que sea) para que se encargue de esto.

Dados los últimos eventos donde se mostró que desde el sitio más choto
hasta el sitio más grande de Rails tiene una falla de seguridad muy
boluda por estos acoples que introduce Rails dándole demasiada
responsabilidad al modelo, creo que queda claro que no es bueno, y no
tenés que estar tomando mi palabra, solo observar.

> (espero que nadie esté enojado con esta discución, en los emails se suelen
> perder las caras y las emociones... paz :-P)
>
> Creo que mientras menos código haya en un programa, más fácil es entenderlo
> (mientras no esté todo ofuscado). Sin requerir ninguna gema y agregando el
> método "amount=", les puedo asegurar que la cantidad del código final es
> mucho menor. Por ende, el proyecto es más entendible.
>

Si. Igual, cuando yo era chico era de creer que menos código equivalía
a más simple, con el tiempo me di cuenta de que esto no es cierto. Un
diseño simple va en tener partes que son fáciles de entender, porque
hacen poco. Estas partes se tienen que combinar bien entre si,
tratando siempre de mantener su simpleza, que aunque puede llegar a
estar relacionada con la cantidad de código, no es necesariamente así.
Es mucho más fácil de entender un componente con unas pocas
responsabilidades que uno con muchas. Entender algo con 4
responsabilidades no es el doble de dificil que entender algo con 2
responsabilidades, es como 5 veces más dificil.

Volviendo a los modelos de ActiveRecord, qué tan bien me decís que los
entendés? A esto sumale los "plugins" que se meten por dentro para
agregar más cosas y hasta cambian las reglas.

> De todas maneras es una creencia mía. No nos olvidemos del mail de Emmanuel
> (defocus), no hice ninguna estadística para demostrarlo. :-)
>
> En general, me gusta que los métodos acepten "lo que venga mientras tenga
> sentido". Pueden ver que eso es cierto en muchas clases core de ruby, y
> hasta en active record (Model.find(1) == Model.find("1")). Por eso me
> pareció sensato que amount=(value) acepte los valores en el formato que
> venga, mientras tenga sentido. Así no hay que repetir la lógica de
> conversión por todas partes (acordarse de ponerla en tal formulario, en tal
> otro).
>

Es conveniente para casos chicos, a medida que tu programa va
creciendo se vuelve un problema. Algo que me molesta de Javascript y
Perl son las coerciones automáticas entre tipos (sumar un número con
una cadena es el caso más común). A primera vista parece simpático y
conveniente, y para cosas chicas la comodidad gana. Ahora, cuando el
programa crece esto se vuelve problemático, agrega montones de casos
borde que antes no tenías, cosas que fallan silenciosamente y por ende
son muy difíciles de depurar.

Si tomás en cuenta que la mayoría del tiempo la pasamos depurando (lo
que incliuye probar que las cosas funcionan, no solo arreglar bugs) y
no escribiendo el código, lo mejor que podemos hacer es no agregar
"magia" que nos dificulte esto.

Ruby (por suerte) no hace coerción automática entre tipos (bueno,
mayormente), no la introduzcamos nosotros.

--
BD

Ary Borenszweig

unread,
Mar 10, 2012, 11:31:55 AM3/10/12
to rub...@googlegroups.com
Si tenés:

class Foo
  def something=(x)
  end
end

al hacer:

foo = Foo.new
foo.something = 2

Ruby lo reescribe como:

foo = Foo.new
foo.something=(2)

Es decir, termina invocando al método "something=". Es sólo una conveniencia sintáctica.

En el ejemplo que di en el mail anterior, sí, se devuelve el valor de @amount. Pero eso es porque siempre se devuelve el resultado de lo último que se ejecuta en el método. Como una asignación devuelve el valor asignado, se devuelve @amount.

Lucas Sallovitz

unread,
Mar 10, 2012, 2:50:03 PM3/10/12
to rub...@googlegroups.com
Me resulta interesante como una cuestión que se puede enunciar tan simple hace brotar tantas reacciones, inclutyendo opiniones como soluciones que dan por sobreentendidos asuntos más básicos. Por ahi está mejor pensarlo más desde 0 sin que las posibilidades estén liminadas por determinado framework o librería, o incluso lenguaje.

En ese sentido creo que el punto más importante son las responsabilidades, y hasta ahora no veo que se haya explicitado que hay más de una responsabilidad en juego, A saber (seguro que hay más y tal vez alguna sea redundante):

Reglas de negocio del atributo ammount: Cosas como "ammount no puede ser múltiplo de 17" pertenecen claramente al dominio de un modelo, deberían ser independientes de cualquier formulario, medio de comunicación con el código usuario, y origen de los datos. Para esto, se debería utilizar una represntación que _solo_ afecte al modelo, debería tener la menor cantidad de consecuencias tanto hacia arriba como hacia abajo en el stack, por ejemplo la distinción entre float y decimal podría ser relevante.

Reglas de negocio de las operaciones sobre ammount (o sobre el modelo que lo contiene): Cosas como "Al crear un artículo, ammount es opcional" o "Al habilitar un artículo para su venta, el ammount no puede estar vacio" podrían definirse (ya es algo que depende del framework) como responsabilidades del mismo modelo, o algún tipo de colección, o controlador (no en el sentido rails) que gestione muchos modelos y sus interacciones, es probable que esta parte interactue horizontalmente con cosas como máquinas de estado y otros modelos, pero debería ser independiente de las capas superiores, la representación de ammount debería estar definida por el modelo.

Notese que hasta aquí no se mencionó la existencia de un usuario, y mucho menos de un mecanismo de transporte como internet, o de sus consecuencias como el uso de formularios html. Así como en los correos anteriores nadie mencionó que base de datos se estaba usando.

Esta próxima responsabilidad podría ser subdividida, pero alcanza para empezar.

Interacciones externas con usuarios u otros actores: Cosas como "mis usuarios pueden tener distintas culturas y usar diferentes representaciones para los números" entrarían en consideración en esta capa, es muy probable que querramos alguna manera "mágica" de que esta capa respete las reglas definidas en las capas inferiores, pero tenemos responsabilidades nuevas como la de convertir el input del usuario en un formato comprendido (en lo posible sin ambiguedad) por las capas inferiores. Tambien es muy probable que querramos que esta sea la capa más barata puesto que es probable que tengamos que hacer varias copias, para arrancar se me ocurren requerimientos como "la aplicación debería andar perfecto con javascript deshabilitado", "si hay javascript habilitado, quiero que todo se haga en una sola _página_ al estilo backbone", "cuando corra mis tests de integración o use mis modelos en la consola, quisiera tener el comportamiento de esta capa, y no solo el del modelo", etc.

No me quiero extender pero si cada uno piensa en los ejemplos, ventajas y desventajas que planteó, verá que toda solución implica compromisos, mezcolanzas entre todas esas responsabilidades, opinionated software (para mi no hay otro tipo) y muy destacadamente limitaciones tecnológicas y costumbres.

Saludos. Lucas.

maxidr

unread,
Mar 12, 2012, 8:54:26 AM3/12/12
to rubysur
Coincido con Lucas respecto a que hay validaciones que son del negocio
y otras que son
referidas a conversiones, que tienen relación con el origen de los
datos cargados por el usuario
u otro sistema (una API, una aplicación web o lo que sea).
Me da a pensar que hay validaciones que son responsabilidad del modelo
(las puramente de negocio) y otras
que son responsabilidad de alguna otra clase que se encargue de las
transformaciones.
Lo complicado está cuando la validación requiere la intervención de
otros modelos.

Otra cosa que no me termina de convencer (aunque claramente es lo
correcto IMHO) es el hecho de tener que
hacer partícipe a otra clase para poder cargar un modelo. O sea, si
yo genero un modelo y alguien mas
tiene que reutilizarlo debo estar seguro que no olvide pasar por la
clase "intermedia" que se encarga de
las validaciones.

Ahora, porque si es una mala práctica delegar la responsabilidad de la
validación a los modelos casi todos los
ORM implementan validación? (model de sequel, datamapper,
ActiveRecord)


On 10 mar, 16:50, Lucas Sallovitz <krusty...@gmail.com> wrote:
> Me resulta interesante como una cuestión que se puede enunciar tan simple
> hace brotar tantas reacciones, inclutyendo opiniones como soluciones que
> dan por sobreentendidos asuntos más básicos. Por ahi está mejor pensarlo
> más desde 0 sin que las posibilidades estén liminadas por determinado
> framework o librería, o incluso lenguaje.
>
> En ese sentido creo que el punto más importante son las responsabilidades,
> y hasta ahora no veo que se haya explicitado que hay más de una
> responsabilidad en juego, A saber (seguro que hay más y tal vez alguna sea
> redundante):
>
> *Reglas de negocio del atributo ammount*: Cosas como "ammount no puede ser
> múltiplo de 17" pertenecen claramente al dominio de un modelo, deberían ser
> independientes de cualquier formulario, medio de comunicación con el código
> usuario, y origen de los datos. Para esto, se debería utilizar una
> represntación que _solo_ afecte al modelo, debería tener la menor cantidad
> de consecuencias tanto hacia arriba como hacia abajo en el stack, por
> ejemplo la distinción entre float y decimal podría ser relevante.
>
> *Reglas de negocio de las operaciones sobre ammount (o sobre el modelo que
> lo contiene)*: Cosas como "Al crear un artículo, ammount es opcional" o "Al
> habilitar un artículo para su venta, el ammount no puede estar vacio"
> podrían definirse (ya es algo que depende del framework) como
> responsabilidades del mismo modelo, o algún tipo de colección, o
> controlador (no en el sentido rails) que gestione muchos modelos y sus
> interacciones, es probable que esta parte interactue horizontalmente con
> cosas como máquinas de estado y otros modelos, pero debería ser
> independiente de las capas superiores, la representación de ammount debería
> estar definida por el modelo.
>
> Notese que hasta aquí no se mencionó la existencia de un usuario, y mucho
> menos de un mecanismo de transporte como internet, o de sus consecuencias
> como el uso de formularios html. Así como en los correos anteriores nadie
> mencionó que base de datos se estaba usando.
>
> Esta próxima responsabilidad podría ser subdividida, pero alcanza para
> empezar.
>
> *Interacciones externas con usuarios u otros actores*: Cosas como "mis

Bruno Deferrari

unread,
Mar 12, 2012, 9:21:16 AM3/12/12
to rub...@googlegroups.com
2012/3/12 maxidr <max...@gmail.com>:

> Coincido con Lucas respecto a que hay validaciones que son del negocio
> y otras que son
> referidas a conversiones, que tienen relación con el origen de los
> datos cargados por el usuario
> u otro sistema (una API, una aplicación web o lo que sea).
> Me da a pensar que hay validaciones que son responsabilidad del modelo
> (las puramente de negocio) y otras
> que son responsabilidad de alguna otra clase que se encargue de las
> transformaciones.
> Lo complicado está cuando la validación requiere la intervención de
> otros modelos.
>
> Otra cosa que no me termina de convencer (aunque claramente es lo
> correcto IMHO) es el hecho de tener que
> hacer partícipe a otra clase para poder cargar un modelo.  O sea, si
> yo genero un modelo y alguien mas
> tiene que reutilizarlo debo estar seguro que no olvide pasar por la
> clase "intermedia" que se encarga de
> las validaciones.
>
> Ahora, porque si es una mala práctica delegar la responsabilidad de la
> validación a los modelos casi todos los
> ORM implementan validación? (model de sequel, datamapper,
> ActiveRecord)
>

No está mal que el modelo valide, es útil para mantener la
consistencia de los datos. El problema es cuando ese modelo implementa
validaciones que son particulares a una interacción particular (los
"workflows" supongo, como decía foca en otro thread).

Mi ORM preferido es SQLAlchemy, y no implementa validaciones como lo
hacen los ORMs que nombraste. Te permite usar código arbitrario para
fijarse que el valor de un field sea válido, pero la mayoría de las
validaciones se hacen a nivel de la base de datos usando constraints
(que podés declararlos desde SQLAlchemy)

http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#simple-validators

Tampoco implementa coerción automática de tipos, pero si querés
hacerlo, va por tu cuenta. Lo podés hacés creando readers/writers
custom:

http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#using-descriptors-and-hybrids

O especificando tus propias reglas en base a TypeDecorator. No hace
nada de eso por default porque generalmente no es una buena idea. Si
lo llegás a necesitar probablemente sea porque tenés algún tipo custom
a nivel del lenguaje y querés mapearlo a algo en la base de datos, lo
cual es muy distinto a andar haciendo mapeos automáticos entre cadenas
y números.

--
BD

Lucas Sallovitz

unread,
Mar 12, 2012, 9:34:52 AM3/12/12
to rub...@googlegroups.com
2012/3/12 maxidr <max...@gmail.com>

 O sea, si
yo genero un modelo y alguien mas
tiene que reutilizarlo debo estar seguro que no olvide pasar por la
clase "intermedia" que se encarga de
las validaciones.

Justamene aca entra en juego el tema de los compromisos y el opinionated software, alguien podría decir que cada modelo debería tener asociado un observer que intercepte (sin conocimiento del modelo ni el cliente) todas las asignaciones pertinentes y aplique las validaciones, pero, que framework que exista hoy permite hacer eso sin forzar las cosas? Otra forma de llamar esto _podría_ ser AOP (aspect oriented programming), pero que yo sepa no es el enfoque principal en los web frameworks de ruby (aunque rails tiene el concepto de observer)

Saludos. Lucas.

Michel Martens

unread,
Mar 12, 2012, 9:51:03 AM3/12/12
to rub...@googlegroups.com
2012/3/10 Lucas Sallovitz <krus...@gmail.com>:

> En ese sentido creo que el punto más importante son las responsabilidades, y
> hasta ahora no veo que se haya explicitado que hay más de una
> responsabilidad en juego, A saber (seguro que hay más y tal vez alguna sea
> redundante):

Lo habíamos hablado en otro thread:
https://groups.google.com/d/msg/rubysur/bDYw3Isi9QU/FqSA_oQLoZ8J

En resumen, se hablaba de tres responsabilidades: integridad de datos,
transformaciones e interacciones.

Michel Martens

unread,
Mar 12, 2012, 10:37:27 AM3/12/12
to rub...@googlegroups.com
2012/3/12 maxidr <max...@gmail.com>:

> Otra cosa que no me termina de convencer (aunque claramente es lo
> correcto IMHO) es el hecho de tener que
> hacer partícipe a otra clase para poder cargar un modelo.  O sea, si
> yo genero un modelo y alguien mas
> tiene que reutilizarlo debo estar seguro que no olvide pasar por la
> clase "intermedia" que se encarga de
> las validaciones.

Creo que esto es cultural, pero es un hábito fácil de cambiar.

> Ahora, porque si es una mala práctica delegar la responsabilidad de la
> validación a los modelos casi todos los
> ORM implementan validación? (model de sequel, datamapper,
> ActiveRecord)

Creo que es por un error que venimos arrastrando desde hace años en el
mundo Ruby.

En DBI sólo era posible declarar tipos, pero nada de validaciones.
Según recuerdo, el primer ORM que hubo en Ruby fue Lafcadio
(http://lafcadio.rubyforge.org/manual.html#id801069), y copiaba de DBI
el tema de los tipos, pero tampoco tenía validaciones. Después salió
Og (está abandonado, pueden leer algo acá:
http://en.wikipedia.org/wiki/Nitro_%28web_framework%29), que es como
el ancestro conceptual de DataMapper. Las primeras versiones
(pre-ActiveRecord) no tenían validaciones, pero apenas salió
ActiveRecord copiaron el formato (class level validations). A partir
de ahí, todos los ORM repitieron ese estilo, hasta que Sequel
implementó las validaciones a nivel de instancia. Ohm hizo lo mismo un
tiempo después.

Ohm incluía el módulo Ohm::Validations y así lo usamos durante años.
Bruno lanzó Bureaucrat a fines del 2009 y se usó en muchos proyectos.
En otros, insistíamos con especificar las interacciones o bien en la
validación de cada modelo (con if/else), o con whitelists al recibir
los parámetros (por ejemplo, hace tres años un colega de Citrusbyte
publicó esta gema: https://github.com/citrusbyte/whitelist).

A mediados del año pasado por fin me convencí de que usar Bureaucrat
era lo correcto, y armé Scrivener para mover ahí todas las
validaciones que estaban en Ohm. La nueva versión de Ohm, que va a
salir dentro de poco tiempo, ya no define validaciones sino que
incluye las de Scrivener. Desde la documentación vamos a sugerir usar
Scrivener directamente.

En Django este problema se solucionó de otra forma desde el principio.
Me parece que las validaciones a nivel de clase que propuso
ActiveRecord hace casi ocho años resultaron mucho más interesantes que
las otras propuestas, y el problema fue que los puntos flojos no eran
fáciles de detectar. Aún hoy a muchos les puede parecer la mejor
opción, con un costo bajo que es lidiar con los casos en donde la
aplicación directa no es posible (mass assignments, roles, etc.).

En fin, esto es más o menos lo que recuerdo.

Diego Algorta

unread,
Mar 12, 2012, 3:22:42 PM3/12/12
to rub...@googlegroups.com
2012/3/12 Michel Martens <sov...@gmail.com>:

Me encantó la recapitulación.

Diego

Reply all
Reply to author
Forward
0 new messages