Ruby Forum Rails-ES > Solución elegante (código en el controlador o en la vista)

Posted by Fernando Calatayud (fernan2)
on 28.02.2008 09:42
Tengo una página en la que muestro información sobre cada tipo de
mensajes, y los últimos mensajes publicados de ese tipo. La primera
aproximación sería cargar en el controlador los tipos, y en la vista
hacer algo así:

<% @tipos.each do |tipo| %>
  --- Mostrar los datos del tipo ---
  <% @mensajes = Mensajes.find(:all, :order => "created_at DESC", :limit
=> 3, :conditions => "tipo_id = #{tipo}" ) %>
  --- Mostrar los últimos mensajes del tipo ---
<% end %>

Esto funciona bien, pero es feo porque mete en la vista una carga de
datos que debería estar en el controlador. ¿Cómo podría resolver esto de
una forma más elegante?

s2 y gracias
Posted by Sergio Gil Pérez de la Manga (Guest)
on 28.02.2008 09:56
(Received via mailing list)
2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>
>  Esto funciona bien, pero es feo porque mete en la vista una carga de
>  datos que debería estar en el controlador. ¿Cómo podría resolver esto de
>  una forma más elegante?
>

Sin más información, creo que la forma correcta a nivel MVC 
sería así:
Modelos:

class Tipo < AR::Base
  has_many :mensajes
end

class Mensaje < AR::Base
  belongs_to :tipo
end

Controlador:

@tipos = Tipo.find(:all)
# opcionalmente, Tipo.find(:all, :include => :mensajes)

Vista:

<% @tipos.each do |tipo| %>
-- Datos de tipo --
<% tipo.mensajes.each do |mensaje| %>
-- Datos de mensaje --
<% end %>
<% end %>

--
Sergio Gil Pérez de la Manga
e-mail > sgilperez@gmail.com
blog > http://www.lacoctelera.com/porras
Posted by Pablo Formoso Estrada (Guest)
on 28.02.2008 09:57
(Received via mailing list)
Hola Fernando

Al ver tipo_id, supongo que tendras una relación por el medio, con lo
que cada tipo ya tiene sus mensajes. para optimizar un poco en el
controlador podría meter algo del estilo:

@tipos = Tipo.find(:all, :include => :mensajes) #carga tambien los
mensajes

Para mostrar por pantalla los mensajes puedes hacer un parcial para los
mensajes
y en la vista te quedaría algo así:

<% for tipo in @tipos %>
    <%= render :partial => "mensajes", :locals => {:mesajes =>
tipo.mensajes} %>
<% end -%>

Esta forma la suelo usar bastante y me funicona bastante bien.

Un saludo.
Posted by Fernando Calatayud (fernan2)
on 28.02.2008 10:08
Perdón, se me ha olvidado un detalle: la tabla de mensajes es enorme 
(varios cientos de miles), y sólo tengo que mostrar los 3 últimos. Un 
acceso como el que proponéis (que obviamente es la solución correcta) 
tendría un coste muy alto y funcionaría mal, por lo que de entrada no es 
viable.

Lo que quizá sí sería viable sería definir una nueva relación, un poco 
más elaborada: que en vez de

Tipo.rb
has_many :mensajes

fuera algo así:
has_many :ultimos_mensajes, :order => 'created_at desc', :limit => 3

Y declarar una clase ultimos_mensajes que herede de mensajes... ¿qué os 
parece eso?
Posted by Daniel Rodriguez Troitiño (Guest)
on 28.02.2008 10:10
(Received via mailing list)
2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>
>  Esto funciona bien, pero es feo porque mete en la vista una carga de
>  datos que debería estar en el controlador. ¿Cómo podría resolver esto de
>  una forma más elegante?
>
>  s2 y gracias
>  --
>

Hay varios problemillas con tu código, obviamente el primero ya lo
comentas tú: pones en la vista lógica de la aplicación, lo que no es
nada recomendable. Pero supongamos que no tenemos problemas con eso
(por ahora).

Para obtener los mensajes de un tipo utilizas :conditions => "tipo_id
= #{tipo}" que tiene dos problemas. En tú caso un problema de
inyección de código es difícil, pero sería recomendable que en esas
cadenas de conditions, permitieras a Rails que te protegiera
utilizando :conditions => { :tipo_id => tipo.id }.

Pero además no necesitas construir tú mismo las condiciones SQL cuando
tienes una relación has_many/belongs_to entre los modelos Tipo y
Mensaje, desde un objeto de clase Tipo puedes utilizar el método
mensajes y desde un objeto de clase Mensaje el método tipo, incluso
con finders y demás cosas que te proporciona Rails. De forma que tu
línea para recupera mensajes sería:

@mensajes = tipo.mensajes.find(:limit => 3, :order => 'created_at DESC')

Mi recomendación sería seguir eliminando lógica de la vista y crear un
nuevo método en mensajes llamado ultimos que precisamente hiciera esa
búsqueda:

class Mensaje
  def self.ultimos(limit = 3) # Por defecto 3, pero se puede modificar
al llamar al método
    find(:limit => limit, :order => 'created_at DESC')
  end
end

Un método que mágicamente Rails te permite utilizar "a través" de una
asociación:
@mensajes = tipo.mensajes.ultimos

El último problema de tu código es el "temido" problema de las n+1
consultas: tú código hace una primera consulta para obtener todos los
tipos, y luego, para cada uno de ellos (n) hace otra consulta para
obtener los tres últimos mensajes. Normalmente ese problema se
resuelve con lo que se denomina "eager_loading", utilizando la 
opción:include de Rails o algo similar, pero no consigo ver con tus
condiciones sobre los mensajes como se puede obtener los últimos tres
de cada tipo directamente desde la base de datos. Quizá alguien con
más experiencia en SQL puede ver una 
solución.
Suerte.
Posted by Sergio Gil Pérez de la Manga (Guest)
on 28.02.2008 12:09
(Received via mailing list)
2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>  has_many :mensajes
>
>  fuera algo así:
>  has_many :ultimos_mensajes, :order => 'created_at desc', :limit => 3
>
>  Y declarar una clase ultimos_mensajes que herede de mensajes... ¿qué os
>  parece eso?

No haría falta crear otra clase, bastaría con especificar la clase en
la relación, algo 
así:
class Tipo < AR::Base
  has_many :mensajes
  has_many :ultimos_mensajes, :class_name => 'Mensaje', :order =>
'created_at desc', :limit => 3
end

Si no me equivoco... (estoy escribiendo el código directamente 
aquí)
--
Sergio Gil Pérez de la Manga
e-mail > sgilperez@gmail.com
blog > http://www.lacoctelera.com/porras
Posted by Fernando Calatayud (fernan2)
on 28.02.2008 14:39
Sergio Gil Pérez de la Manga wrote:
> No har�a falta crear otra clase, bastar�a con especificar la clase en
> la relaci�n, algo 
> as�:
class Tipo < AR::Base
>   has_many :mensajes
>   has_many :ultimos_mensajes, :class_name => 'Mensaje', :order =>
> 'created_at desc', :limit => 3
> end
> 
> Si no me equivoco... (estoy escribiendo el c�digo directamente 
> aqu�)
--
> Sergio Gil P�rez de la Manga
> e-mail > sgilperez@gmail.com
> blog > http://www.lacoctelera.com/porras

Sergio, muchas gracias, tu solución es cojonuda... pero me temo que en 
términos de rendimiento es un desastre. Aunque no lo había mencionado 
antes, por no salirnos del problema, desde mensajes se linka también con 
la tabla usuarios (el autor del mensaje). Y al probar esto:

@tipos = Tipo.find(:all, :order => 'position', :include => 
{:ultimos_contenidos_3 => :usuario})

se genera una consulta tan brutal que mysql lleva un cuarto de hora y 
aún no me ha devuelto el resultado.

No puedo pasar por abstracciones de rails porque no generan consultas 
óptimas, y con estas tablas tan grandes el que sea o no óptima es 
crítico. Por lo tanto, no tengo más remedio que montar más o menos "a 
mano" la consulta. Voy a probar a hacerlo con un método de la clase, 
como sugiere Daniel, a ver qué resultado da... ya os contaré.

s2
Posted by Ruben Davila (rdavila)
on 28.02.2008 17:24
(Received via mailing list)
Hola fernando, la solucion que te dio pablo me parece buena, solo 
cambiar lo
siguiente:

<% for tipo in @tipos %>
    <%= render :partial => "mensajes", :locals => {:mesajes => 
tipo.mensajes}
%>
<% end -%>

por;

<% for tipo in @tipos %>
    <%= render :partial => "mensaje", :collection => tipo.mensajes[0..2] 
%>
<% end -%>

Aqui hay varias cosas importantes, primero al usar collection tendrias 
la
variable local mensaje disponible automaticamnete en el partial.

Ahora por el lado del SQL, veo un problema y es que quieras o no estas
cargando todos los mensajes asociados al tipo; ahora como el metodo
'mensajes' te devuelve un array entonces puedes jugar y seleccionar la
cantidad de elementos que necesites, aunque tal vez no estan ordenados 
como
tu quieras, eso ya es cosa de que juegues un poco mas con los metodos 
del
array.

Otra solucion tal vez un poco mas pragmatica seria usar una sentencia 
SQL
pura y dura en tu controlador para obtener los mensajes, y entonces te
evitarias de liarte tanto en las vistas.

Saludos.



El día 28/02/08, Fernando Calatayud <ruby-forum-incoming@andreas-s.net>
escribió:
Posted by Fernando Calatayud (fernan2)
on 28.02.2008 17:36
Ruben Davila wrote:
> Hola fernando, la solucion que te dio pablo me parece buena, solo 
> cambiar lo
> siguiente:
> 
> <% for tipo in @tipos %>
>     <%= render :partial => "mensajes", :locals => {:mesajes => 
> tipo.mensajes}
> %>
> <% end -%>
> 
> por;
> 
> <% for tipo in @tipos %>
>     <%= render :partial => "mensaje", :collection => tipo.mensajes[0..2] 
> %>
> <% end -%>
> 
> Aqui hay varias cosas importantes, primero al usar collection tendrias 
> la
> variable local mensaje disponible automaticamnete en el partial.
> 
> Ahora por el lado del SQL, veo un problema y es que quieras o no estas
> cargando todos los mensajes asociados al tipo; ahora como el metodo
> 'mensajes' te devuelve un array entonces puedes jugar y seleccionar la
> cantidad de elementos que necesites, aunque tal vez no estan ordenados 
> como
> tu quieras, eso ya es cosa de que juegues un poco mas con los metodos 
> del
> array.
> 
> Otra solucion tal vez un poco mas pragmatica seria usar una sentencia 
> SQL
> pura y dura en tu controlador para obtener los mensajes, y entonces te
> evitarias de liarte tanto en las vistas.
> 
> Saludos.
> 
> 
> 
> El día 28/02/08, Fernando Calatayud <ruby-forum-incoming@andreas-s.net>
> escribió:

Yo no puedo estar cargando todos los mensajes, hablamos de más de 
100.000!! Tengo que irme a SQL propia, pero el tema es que son n SQLs, y 
a ver cómo meto un nº indeterminado de SQLs en el controlador... por eso 
el primer intento ha sido meterla en la vista (que es una chapuza, pero 
va como una bala).

s2
Posted by Ruben Davila (rdavila)
on 28.02.2008 17:56
(Received via mailing list)
Fernando comprendo exactamente tu situacion, por eso me temia que la
solución que te di no iba a ser la mas óptima, ademas que no habia leido 
tus
otros mensajes, bueno ya que necesitas bastante optimización de la 
consulta,
tal vez este slide[1] te pueda servir.

Saludos

[1]:
http://work.rowanhick.com/wp-content/uploads/2008/02/activerecordpresentationfeb12.pdf

El día 28/02/08, Fernando Calatayud <ruby-forum-incoming@andreas-s.net>
escribió:
Posted by Fernando Calatayud (fernan2)
on 28.02.2008 18:14
Daniel Rodriguez Troitiño wrote:
> 2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>>
>>  Esto funciona bien, pero es feo porque mete en la vista una carga de
>>  datos que deber�a estar en el controlador. �C�mo podr�a resolver esto de
>>  una forma m�s elegante?
>>
>>  s2 y gracias
>>  --
>>
> 
> Hay varios problemillas con tu c�digo, obviamente el primero ya lo
> comentas t�: pones en la vista l�gica de la aplicaci�n, lo que no es
> nada recomendable. Pero supongamos que no tenemos problemas con eso
> (por ahora).
> 
> Para obtener los mensajes de un tipo utilizas :conditions => "tipo_id
> = #{tipo}" que tiene dos problemas. En t� caso un problema de
> inyecci�n de c�digo es dif�cil, pero ser�a recomendable que en esas
> cadenas de conditions, permitieras a Rails que te protegiera
> utilizando :conditions => { :tipo_id => tipo.id }.
> 
> Pero adem�s no necesitas construir t� mismo las condiciones SQL cuando
> tienes una relaci�n has_many/belongs_to entre los modelos Tipo y
> Mensaje, desde un objeto de clase Tipo puedes utilizar el m�todo
> mensajes y desde un objeto de clase Mensaje el m�todo tipo, incluso
> con finders y dem�s cosas que te proporciona Rails. De forma que tu
> l�nea para recupera mensajes ser�a:
> 
> @mensajes = tipo.mensajes.find(:limit => 3, :order => 'created_at DESC')
> 
> Mi recomendaci�n ser�a seguir eliminando l�gica de la vista y crear un
> nuevo m�todo en mensajes llamado ultimos que precisamente hiciera esa
> b�squeda:
> 
> class Mensaje
>   def self.ultimos(limit = 3) # Por defecto 3, pero se puede modificar
> al llamar al m�todo
>     find(:limit => limit, :order => 'created_at DESC')
>   end
> end
> 
> Un m�todo que m�gicamente Rails te permite utilizar "a trav�s" de una
> asociaci�n:
@mensajes = tipo.mensajes.ultimos
> 
> El �ltimo problema de tu c�digo es el "temido" problema de las n+1
> consultas: t� c�digo hace una primera consulta para obtener todos los
> tipos, y luego, para cada uno de ellos (n) hace otra consulta para
> obtener los tres �ltimos mensajes. Normalmente ese problema se
> resuelve con lo que se denomina "eager_loading", utilizando la 
> opci�n:include de Rails o algo similar, pero no consigo ver con tus
> condiciones sobre los mensajes como se puede obtener los �ltimos tres
> de cada tipo directamente desde la base de datos. Quiz� alguien con
> m�s experiencia en SQL puede ver una 
> soluci�n.
Suerte.


Daniel, gracias por todas las correcciones.
- Efectivamente, aquí no hay posibilidad de inyección de código, los 
tipos los maneja el administrador.
- Respecto a construir las condiciones SQL yo mismo, sí necesito 
hacerlo: cuestión de rendimiento crítico. Ir del tipo a los mensajes es 
lentísimo, mientras que ir directamente a los mensajes, filtrando por 
tipo, es instantáneo... incluso aunque sea n+1 (en este caso, n es 
pequeño).
- Sin embargo, tu idea de mover el código a la clase es muy buena... 
sólo que por tema de eficiencia, sería en la clase Tipo y no el la 
Mensaje (si lo meto en mensaje, volvemos al mismo problema). Metiendo en 
Tipo.rb esto:

  def ultimos_contenidos(limit = 3)     # Por defecto 3, pero se puede 
modificar al llamar al método
    Mensaje.find(:all, :limit => limit, :order => "contenidos.created_at 
DESC", :conditions => "tipo_id = #{self.id} and publicado")
  end

en la vista ya puedo hacer:

  tipo.ultimos_contenidos.each do |msg|

Y va bien.
Posted by Daniel Rodriguez Troitiño (Guest)
on 28.02.2008 22:11
(Received via mailing list)
2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>  >
>  >
>  > nuevo m�todo en mensajes llamado ultimos que precisamente hiciera esa
>  > asociaci�n:
>  > m�s experiencia en SQL puede ver una
>  tipo, es instantáneo... incluso aunque sea n+1 (en este caso, n es
>   end
> Posted via http://www.ruby-forum.com/.
>  _______________________________________________
>  Ror-es mailing list
>  Ror-es@lists.simplelogica.net
>  http://lists.simplelogica.net/mailman/listinfo/ror-es
>

Cuando utilizas un método de clase (los que llevan self) a través de
una asociación Rails añade automáticamente la condición que tu
escribes para tipo_id (tendrías que añadir la de publicado al método
de clase, como antes no la ponías en mi solución no la puse). No creo
que el código SQL generado de ese modo y el código que se genera con
tu método sea muy diferente.

De todas formas, por lo que hablas, sobre la cantidad de datos y
demás, no me parecen tantos como para que MySQL tarde tanto en
cargarlos. ¿Tienes bien puestos los indices de las tablas?
Necesitarías al menos un índice para tipo_id y otro para created_at en
la tabla mensajes. Sin esos dos índices MySQL va a tener que leer cada
uno de los registros para encontrar los que quieres.

Si crees que es la base de datos la que no da la talla podrías
utilizar el plugin Query Analyzer
<http://agilewebdevelopment.com/plugins/query_analyzer> para comprobar
que tus queries SQL están utilizando los índices correctamente.

Suerte.
Posted by Fernando Calatayud (fernan2)
on 29.02.2008 09:38
Daniel Rodriguez Troitiño wrote:
> De todas formas, por lo que hablas, sobre la cantidad de datos y
> demás, no me parecen tantos como para que MySQL tarde tanto en
> cargarlos. ¿Tienes bien puestos los indices de las tablas?
> Necesitarías al menos un índice para tipo_id y otro para created_at en
> la tabla mensajes. Sin esos dos índices MySQL va a tener que leer cada
> uno de los registros para encontrar los que quieres.
> 
> Si crees que es la base de datos la que no da la talla podrías
> utilizar el plugin Query Analyzer
> <http://agilewebdevelopment.com/plugins/query_analyzer> para comprobar
> que tus queries SQL están utilizando los índices correctamente.
> 
> Suerte.

Los índices están bien puestos: en tipo_id, en created_at y en 
usuario_id. Pero con la consulta "estandar" de ruby, es un desastre de 
rendimiento, mientras que al partirlo en n+1 ya va bien. ¿La causa? Pues 
aunque sólo había puesto lo que me parecía más relevante, la consulta 
sobre tipos lleva más cosas; no sé si será por eso el desastre de 
algunas combinaciones. Esta es la verdadera consulta, completa:

    @tipos = Tipo.find(:all, :order => 'tipos.position', :conditions => 
"clase_id = 1",
                    :select => "tipos.id, tipos.nombre_completo, 
url_tipo, t.url_clase, tipos.descripcion,
                              (SELECT count(*) FROM mensajes WHERE 
tipo_id = tipos.id and publicado and inicial_id = 0) as temas,
                              (SELECT count(*) FROM mensajes WHERE 
tipo_id = tipos.id and publicado and inicial_id <> 0) as respuestas",
                    :joins => "join clase t on tipos.clase_id = t.id")

Me temo que esos SELECT count pueden tener un impacto dramático si 
empiezan a lanzarse de forma repetida...

s2
Posted by Javier Martínez (Guest)
on 01.03.2008 00:57
(Received via mailing list)
Perdonad por desviarme del tema, pero de donde puedo encontrar
informacion acerca de este tipo de relaciones (la de 
"ultimos_mensajes")?

class Tipo < AR::Base
  has_many :mensajes
  *has_many :ultimos_mensajes, :class_name => 'Mensaje', :order =>
'created_at desc', :limit => 3*
end


Siempre habia visto relaciones normales con otros objectos, pero nunca
en las que especificabas esos parametros.

Gracias y disculpas de nuevo por desviarme del tema principal.



Sergio Gil Pérez de la Manga 
escribió:> 2008/2/28 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
Posted by Daniel Rodriguez Troitiño (Guest)
on 01.03.2008 02:56
(Received via mailing list)
2008/2/29 Fernando Calatayud <ruby-forum-incoming@andreas-s.net>:
>  > <http://agilewebdevelopment.com/plugins/query_analyzer> para comprobar
>
>  Me temo que esos SELECT count pueden tener un impacto dramático si
>  empiezan a lanzarse de forma repetida...
>
>
>
>  s2

En las sentencias "select count..." creo que los índices son
necesarios sobre las tres columnas a la vez, pero podría equivocarme.
Te recomiendo ejecutar esas sentencias "select count..." sobre tu base
de datos con "explain select count..." para ver si MySQL utiliza
correctamente los índices.

De cualquier manera, si MySQL utiliza bien los índices y aún te parece
que la eficiencia no es la adecuanda lo más recomendable 
seríade-normalizar, es decir, incluir en la tabla "tipos" dos columnas
"temas_count" y "respuestas_count" que se actualizaran cuando se
crearan nuevos mensajes o se destruyeran los ya existentes.

Suerte.
Posted by Daniel Rodriguez Troitiño (Guest)
on 01.03.2008 02:58
(Received via mailing list)
2008/2/29 Javier Martínez <ecentinela@gmail.com>:
>
>  Siempre habia visto relaciones normales con otros objectos, pero nunca
>  en las que especificabas esos parametros.
>
>  Gracias y disculpas de nuevo por desviarme del tema principal.
>
>

Obviamente en la ayuda de Rails:
<http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001103>
Posted by Jaime Iniesta (Guest)
on 01.03.2008 17:17
(Received via mailing list)
El día 28/02/08, Fernando Calatayud <ruby-forum-incoming@andreas-s.net>
escribió:
>
> Perdón, se me ha olvidado un detalle: la tabla de mensajes es enorme
> (varios cientos de miles), y sólo tengo que mostrar los 3 últimos. Un
> acceso como el que proponéis (que obviamente es la solución correcta)
> tendría un coste muy alto y funcionaría mal, por lo que de entrada no es
> viable.
>

En la Conferencia Rails 2007, Pablo Delgado expuso una charla super
interesante sobre escalabilidad:

  http://video.google.es/videoplay?docid=-3750147543196804850
  http://2007.conferenciarails.org/archivos/pablo_delgado_escalabilidad.pdf

Uno de los trucos para mostrar rápidamente los x últimos registros de 
una
tabla enorme era crear un índice inverso, en plan:

  reverse_created_at = 2030 - created_at

Así el MySQL los encuentra mucho más rápidamente, por lo visto.
Posted by Fernando Calatayud (fernan2)
on 03.03.2008 17:08
Daniel Rodriguez Troitiño wrote:
> De cualquier manera, si MySQL utiliza bien los �ndices y a�n te parece
> que la eficiencia no es la adecuanda lo m�s recomendable 
> ser�ade-normalizar, es decir, incluir en la tabla "tipos" dos columnas
> "temas_count" y "respuestas_count" que se actualizaran cuando se
> crearan nuevos mensajes o se destruyeran los ya existentes.
> 
> Suerte.

Por ahí voy a tirar... un poco de de-normalización, cacheando los datos 
intermedios, y ya tengo un rendimiento óptimo... y eso sí, un foco de 
problemas potenciales si meto la pata. Pero es lo que toca para este 
caso, creo.

s2
Posted by Fernando Calatayud (fernan2)
on 03.03.2008 17:10
Jaime Iniesta wrote:
> 
> En la Conferencia Rails 2007, Pablo Delgado expuso una charla super
> interesante sobre escalabilidad:
> 
>   http://video.google.es/videoplay?docid=-3750147543196804850
>   http://2007.conferenciarails.org/archivos/pablo_delgado_escalabilidad.pdf
> 
> Uno de los trucos para mostrar rápidamente los x últimos registros de 
> una
> tabla enorme era crear un índice inverso, en plan:
> 
>   reverse_created_at = 2030 - created_at
> 
> Así el MySQL los encuentra mucho más rápidamente, por lo visto.

Un enlace muy interesante, desde luego!! lo del índice inverso, si 
funciona, me serviría para varios sitios... aunque mejor sólo lo montaré 
donde sea realmente necesario.

s2 y gracias!!
Posted by Manuel González Noriega (Guest)
on 03.03.2008 17:38
(Received via mailing list)
On 03/03/2008, Fernando Calatayud <ruby-forum-incoming@andreas-s.net> 
wrote:
>  intermedios, y ya tengo un rendimiento óptimo... y eso sí, un foco de
>  problemas potenciales si meto la pata. Pero es lo que toca para este
>  caso, creo.
>

Ya lo dijo Cal Henderson

http://www.google.es/search?hl=en&client=firefox-a&rls=org.mozilla%3Aen-US%3Aofficial&hs=FfD&q=normalized+data+is+for+sissies&btnG=Search


--
Manuel, que
piensa que eres una excelente persona y medra en torno a
http://simplelogica.net y/o http://simplelogica.net/logicola/
Recuerda comer mucha fruta y verdura.
Posted by Jaime Iniesta (Guest)
on 13.08.2008 19:49
(Received via mailing list)
El 1 de marzo de 2008 18:13, Jaime Iniesta 
<jaimeiniesta@gmail.com>escribió:

>
>   reverse_created_at = 2030 - created_at
>
> Así el MySQL los encuentra mucho más rápidamente, por lo visto.
>

Estoy probando esta técnica que expuso Pablo Delgado para mostrar
rápidamente los X últimos registros de las tablas... lo que él expone es 
que
como MySQL tarda mucho en ordenar una tabla a la inversa, si por ejemplo
quieres sacar los últimos 10 posts de una tabla de un millón de posts, 
es
crear un índice inverso respecto a la fecha de creación, en plan

reverse_created_at = 2030 - created_at

Bueno, lo primero con lo que me encuentro al llevarlo a la práctica es 
que
no se puede restar así directamente, 2030 es un integer y created_at una
fecha así que lo pongo así:

class Post < ActiveRecord::Base

  before_save :update_reverse

  def update_reverse
    self.reverse_created_at = (('2030/1/1'.to_datetime) -
(self.created_at.to_datetime))
    self.reverse_updated_at = (('2030/1/1'.to_datetime) -
(self.updated_at.to_datetime))
  end

end

Pero esto no me funciona, porque de la resta me devuelve un tipo de 
datos
Rational que no se guarda en el MySQL...

 Rational(676652623, 86400)

¿Qué estoy haciendo mal?
Posted by Isaac Feliu Pérez (Guest)
on 13.08.2008 20:06
(Received via mailing list)
Jaime,

provando en el script/console, con rails 2.1 me sale esto


 >> Time.zone.at(Time.zone.parse('2030-01-01') - u.created_at)
=> Thu, 28 Nov 1991 06:51:53 CET +01:00

Usease, utilizando el time.zone.at conviertes el numero de nuevo a
fecha.

Espero que te sirva!

Salutaciones,
--
Isaac Feliu
Posted by Isaac Feliu Pérez (Guest)
on 13.08.2008 20:09
(Received via mailing list)
Pensandolo mejor, otra opción elegante sería sobreescribir el método
"-" de la classe Integer para que si se le pasa un objeto de tipo
datetime (o el que corresponda, como ActiveSupport::TimeWithZone), te
haga la operación y devuelva un objeto de tipo datetime, del palo

module CoreHacks
    module IntegerHacks
       def -(other)
          if other.is_a?(ActiveSupport::TimeWithZone)
              Time.zone.at(Time.zone.parse("#{self}-01-01}") - other)
          else
            super other
          end
       end
     end
   end
end

Integer.send :include, CoreHacks::IntegerHacks

así deberias poder hacer 2030 - created_at

¡¡Cuidado que es una hipotesis y no lo he probado!!

Salutaciones,
--
Isaac Feliu
Posted by Jaime Iniesta (Guest)
on 13.08.2008 20:13
(Received via mailing list)
El 13 de agosto de 2008 20:02, Isaac Feliu Pérez
<isaac.feliu@gmail.com>escribió:

> Jaime,
> provando en el script/console, con rails 2.1 me sale esto
>
>
> >> Time.zone.at(Time.zone.parse('2030-01-01') - u.created_at)
> => Thu, 28 Nov 1991 06:51:53 CET +01:00
>
> Usease, utilizando el time.zone.at conviertes el numero de nuevo a fecha.
>
>
Vaya, estoy en rails 2.0.1 y no tengo Time zones... pero gracias!
Posted by Amaia Castro (Guest)
on 13.08.2008 23:29
(Received via mailing list)
Hola

He hecho unas pruebecillas en la consola de rails 2.0.2 y parece que si
restas dos DateTime da un Rational, pero si restas dos Time da un Float,
que no tendría que dar problemas con la BD.
Si created_at es un datetime puedes convertirlo en Time con to_time.
A lo mejor con eso te sirve.

Loading development environment (Rails 2.0.2)
>> a = Time.utc(year=2030)
=> Tue Jan 01 00:00:00 UTC 2030
>> b = User.find(:first).created_at
=> Wed Jan 02 17:38:14 +0100 2008
>> a.class
=> Time
>> b.class
=> Time
>> a - b 
=> 694164106.0
>> c = a - b 
=> 694164106.0
>> c.class
=> Float
>> 
>> d = DateTime.new(year=2008, month=4)
=> Tue, 01 abr 2008 00:00:00 +0000
>> d.class
=> DateTime
>> d.to_time.class
=> Time


Saludos
Amaia


--
Amaia Castro
Dabne Tecnologías de la Información - www.dabne.net -
http://blog.dabne.net
~~*~~
Buscador de Subvenciones - http://buscaboe.dabne.net
Apuntes de Rails - http://apuntesderails.amaiac.net
~~*~~
Posted by Emili Parreño (Guest)
on 14.08.2008 04:04
(Received via mailing list)
Yo creo que la solución más simple es pasar el año 2030 a segundos y el
created_at también y así tienes un integer, que seguramente mysql lo 
ordena
más rápido que un datetime. Por ejemplo
Time.utc(2030).to_i - created_at.to_i # Time.utc(2030) crea un objeto 
Time
"01/01/2030 00:00"

y andando, ya tienes un índice reverso en formato integer. En el 2030 ya
veremos que pasa :)

El 13 de agosto de 2008 23:28, Amaia Castro <amaia@dabne.net> escribió:
Posted by Jaime Iniesta (Guest)
on 14.08.2008 08:23
(Received via mailing list)
El 14 de agosto de 2008 4:04, Emili Parreño <emili@abecedata.com> 
escribió:

> Yo creo que la solución más simple es pasar el año 2030 a segundos y el
> created_at también y así tienes un integer, que seguramente mysql lo ordena
> más rápido que un datetime.
>

Ahí estamos compañero! Se trata de que el campo reverse_created_at ha de 
ser
un campo numérico y no de fecha como habíamos planificado en un 
principio,
Emili :)

El mismo Pablete me ha explicado la técnica personalmente desde un 
cibercafé
italiano :)

###################################
Si por ejemplo restas 30 de agosto a 13 de agosto, el resultado es un
numero, el numero de segundos (si no me equivoco, o milisegundos)
entre el 30 (futuro) y 13 (presente) . El resultado sera 17dias*24*60
en segundos.

Da un rational o un integer o un numero, como quieras. El echo es que
lo que tiene fecha mas vieja sera un numero grande, mientras que el
mas reciente sera un numero mas pequeno. CONCLUSION el campo
reverse_created_at es INTEGER o lo que sea...

Debes crear un indice con una migracion o a mano de la siguiente manera

ALTER TABLE posts ADD INDEX reverse_created_at ( reverse_created_at )
ALTER TABLE posts ADD INDEX reverse_updated_at ( reverse_updated_at)


Y cuando debas buscar los ultimos posts le pones

Post.find(:all, ...................., :order=> "reverse_updated_at" ,
:limit=>5)

Los indices numericos van mucho mas rapidos ;)
###################################

Voy a aplicarlo...
Posted by Fernando Guillen (Guest)
on 14.08.2008 09:12
(Received via mailing list)
Y por qué no, simplemente, como me decía nuestro amigo Blat:

      self.created_at_inverse = Time.local(2038,01,01,00,00,00).to_i -
Time.now.to_i

O optimizando:

      self.created_at_inverse = 2145913200 - Time.now.to_i

O como he resumido yo:

      self.created_at_inverse = -(Time.now.to_i)

No entiendo esa fijación de que este campo sea siempre positivo (hasta el 
2038).

Yo al final he dejado mi callback 
así:
Este es mi callback (before_save):
    def update_created_at_inverse
      self.created_at_inverse = -(self.created_at.to_i)
    end

Saludos
f.

El día 14 de agosto de 2008 8:23, Jaime Iniesta
<jaimeiniesta@gmail.com> 
escribi
Posted by Jaime Iniesta (Guest)
on 14.08.2008 09:36
(Received via mailing list)
El 14 de agosto de 2008 9:12, Fernando Guillen
<fguillen.mail@gmail.com>escribió:

> Y por qué no, simplemente, como me decía nuestro amigo Blat:
>
>      self.created_at_inverse = Time.local(2038,01,01,00,00,00).to_i -
> Time.now.to_i
>
> O optimizando:
>
>      self.created_at_inverse = 2145913200 - Time.now.to_i


Pues también, más rápido...


>
>
> O como he resumido yo:
>
>      self.created_at_inverse = -(Time.now.to_i)
>
> No entiendo esa fijación de que este campo sea siempre positivo (hasta el
> 2038).
>

Según Pablete, lo de que el índice sea positivo es porque MySQL tarda en
ordenar a la inversa. O sea que si se lo das ya en positivo, no tiene 
que
darle la vuelta a la tabla:

  2030 - 2008 = 2022
  2030 - 2009 = 2021
  2030 - 2010 = 2020
  2030 - 2011 = 2019

que ya vienen ordenados como nos interesa: 2019, 2020, 2021, 2022

En cambio en tu solución:

  -2008
  -2009
  -2010
  -2011

que ordenados queda -2011, -2010, -2009, -2008 con lo que MySQL tiene 
que
darle la vuelta para tenerlos como nos interesa, lo más reciente 
primero.

Supongo que en ambos casos se nota la optimización al ser un integer en
lugar de una fecha pero me parece que la gracia está en que ya el indice
venga ordenado y así evitarle el trabajo de darle la vuelta.

Tiene la desventaja de que en el 2030 deja de funcionar bien el orden, 
pero
ya me he apuntado en mi calendario del 2029 repasar este truco y
actualizarlo :)
Posted by Fernando Guillen (Guest)
on 14.08.2008 10:34
(Received via mailing list)
El día 14 de agosto de 2008 9:35, Jaime Iniesta
<jaimeiniesta@gmail.com> 
escribió:> Según Pablete, lo de que el índice sea positivo es porque MySQL tarda en
> ordenar a la inversa.

Estamos de acuerdo en que 'ordenar de manera inversa' quiere decir
'ordenar de manera DESC'. Ya que los índices se crean de manera
natural ordenados de manera ASC.

> En cambio en tu solución:
>
>   -2008
>   -2009
>   -2010
>   -2011
>
> que ordenados queda -2011, -2010, -2009, -2008 con lo que MySQL tiene que
> darle la vuelta para tenerlos como nos interesa, lo más reciente primero.

No hay que olvidar que son números negativos por lo que el orden
natural de estos (es decir ASC) sería:

-2008
-2009
-2010
-2011

Que creo que es lo que estamos buscando:

mysql> create table prueba( campo integer );
mysql> insert prueba ( campo ) values ( -2008 );
mysql> insert prueba ( campo ) values ( -2009 );
mysql> insert prueba ( campo ) values ( -2010 );
mysql> insert prueba ( campo ) values ( -2011 );
mysql> select * from prueba order by campo asc;
+-------+
| campo |
+-------+
| -2011 |
| -2010 |
| -2009 |
| -2008 |
+-------+

Incluso con la historia de restar 2030 - Time.now seguiría funcionando
más allá del 2030. puesto que 1 > 0 > -1.

¿Estoy perdido? .. ya me he tomado el café eh¡ :)

Saludos
f.
Posted by Jaime Iniesta (Guest)
on 14.08.2008 10:44
(Received via mailing list)
El 14 de agosto de 2008 10:34, Fernando Guillen
<fguillen.mail@gmail.com>escribió:

> >
>
> mysql> insert prueba ( campo ) values ( -2010 );
>
> Incluso con la historia de restar 2030 - Time.now seguiría funcionando
> más allá del 2030. puesto que 1 > 0 > -1.


Jostris! Pues tienes razón! Me equivoqué yo al afirmar:

"que ordenados queda -2011, -2010, -2009, -2008 con lo que MySQL tiene 
que
darle la vuelta para tenerlos como nos interesa, lo más reciente 
primero."

porque ya están ordenados como nos interesa, lo más reciente primero... 
y
así MySQL no tiene que darle la vuelta... ni tenemos que hacer una resta
para actualizar... y todo seguirá funcionando pasado el 2.030... (lo 
siento
@emili pero habrá que sacar dinero para la jubilación de otro lado :P )


>
> ¿Estoy perdido? .. ya me he tomado el café eh¡ :)


Yo si que estaba perdido y el café todavía no había llegado arriba!