sábado, 28 de marzo de 2009

Combinación de named_scopes en rails

Cuando hay que realizar un filtrado de resultados en una web con muchas posibles combinaciones podemos acabar con muchas líneas de código para obtener una funcionalidad no demasiado compleja.

En rails, la mayoría de los filtrados se hacen con named_scopes, queda parcialmente resuelto el problema, aún así quedan resolver como aplicar cada combinación de named_scopes según el filtrado que necesite el usuario.

Suponiendo que tenemos un modelo Post como el siguiente:

class Post < ActiveRecord::Base
named_scope :all
named_scope :published, :conditions => {:published => true}
named_scope :from_user, :conditions => {:kind => 'user'}
named_scope :from_admin, :conditions => {:kind => 'admin'}
end


Podemos resolver el problema de aplicar filtros combinados de la siguiente forma usando inject:

class PostsController < ApplicationController
def index
filter = (params[:filter] or String.new)
filter = filter.split(',').map(&:to_sym).select{|f| Post.scopes.include?(f)}
filter << :all

@posts = filter.inject(Post){|model, nscope| model.send(nscope)}
end
end

En primer lugar obtenemos la lista de named_scopes a aplicar y filtramos los no válidos, en segundo con inject se los aplicamos al modelo.

Con ese código tan simple podemos usar rutas del tipo:

http://server/posts
http://server/posts?filter=published
http://server/posts?filter=published,from_user

domingo, 1 de marzo de 2009

Optimización del tiempo de carga de webs

Dentro del tiempo total desde que se hace una petición a una página web hasta que se termina de dibujar en el navegador, gran parte corresponde a los distintos recursos externos que hay que recuperar: javascript, css e imágenes.

Los ficheros javascript y css tienen una propiedad interesante, si se concatenan varios y se envían en un solo fichero no debe influir en el resultado final.
Sin embargo, si se concatenan, se pueden cargar con una sola petición, con el consiguiente aumento de rendimiento.

El método que rails proporciona para hacer esto de forma automática es el siguiente (solo en modo producción):

<%= javascript_include_tag 'first.js', 'second.js', :cache => '1_2' %>

Sin embargo esto tiene un problema, normalmente en una aplicación según la página se carga unos ficheros u otros, no es muy práctico ir dándole claves a todas las combinaciones.

Se pueden programar dos helpers para dar las claves de forma automática:

<%= cached_javascript_include_tag 'first.js', 'second.js' %>
<%= cached_stylesheet_link_tag 'first.css', 'second.css' %>

Es importante que las versiones cacheadas tanto de css como de javascript no estén en el svn, a la hora de hacer un despliegue se regenerarán usando la nueva versión de los ficheros.

Aún así, si se quiere que se genere de nuevo la versión cacheada para una página sin desplegar la aplicación ni borrar los ficheros antiguos, se puede usar un sistema de versiones.

<%= cached_javascript_include_tag 'first.js?v=1', 'second.js?v=3' %>

El código de los helpers es el siguiente:

module ApplicationHelper
def cached_javascript_include_tag(*args)
cached_args!(*args)
javascript_include_tag(*args)
end

def cached_stylesheet_link_tag(*args)
cached_args!(*args)
stylesheet_link_tag(*args)
end

def cached_args!(*args)
options = args.extract_options!.stringify_keys

cache_entry = args.map(&:to_s).sort.join('_').gsub!('?', '_')
options[:cache] = cache_entry

args << options
end
end