miércoles, 3 de junio de 2009

Definición de named_scopes dependientes del tiempo actual

Si tenemos una clase ActiveRecord como la siguiente:

class Post < ActiveRecord::Base
named_scope :test, :conditions => ['updated_at < ?', Time.now]
end

En modo development funciona sin problemas, sin embargo en modo production la consulta que se genera no es la esperada.

Al arrancar rails en modo production se cachean las clases, al cargar este modelo en concreto se evalúa Time.now haciendo que para todas las consultas se coja como fecha la hora en la que se ha cargado el modelo.

El descrito anteriormente no es el comportamiento que esperaríamos al definir el named_scope, sino que debería obtener la fecha y hora actual para cada consulta, no en la definición.

Revisando la definición de los named_scopes nos encontramos lo siguiente:

def named_scope(name, options = {}, &block)
name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
options
when Proc
options.call(*args)
end, &block)
end
(class << self; self end).instance_eval do
define_method name do |*args|
scopes[name].call(self, *args)
end
end
end


En nuestro caso pasamos un hash como opciones, por lo que efectivamente se evaluará el Time.now solo en la definición de dicho hash.

Si en lugar de eso definimos un objeto de tipo Proc se evaluará cada vez.

Por lo tanto para arreglar el comportamiento del primer ejemplo deberíamos escribirlo como:

class Post < ActiveRecord::Base
named_scope :test, lambda{{:conditions => ['updated_at < ?', Time.now]}}
end

domingo, 10 de mayo de 2009

Aplicación de consola para twitter

Acabo de lanzar un proyecto en github para interaccionar con twitter desde consola.

Por ahora incluye la funcionalidad básica: cambio de estado, consulta de estado de amigos, login y mensajería.

La interfaz de comandos es muy similar al clásico IRC.

La url del proyecto es la siguiente: twit

viernes, 1 de mayo de 2009

Añadiendo estado a los elementos del DOM

En aplicaciones complejas con javascript para guardar el estado de cada elemento se suele modificar alguna propiedad del elemento que queramos manipular.

Sin embargo esta práctica es poco adecuada y no demasiado flexible, ya que no podemos guardar tipos de datos complejos.

jQuery ya prevee que podemos necesitar esta funcionalidad y nos proporciona la función data para guardar información de cada elemento.

Por ejemplo:

$('a').data('activated_links', false);
....
var footer_links = $('.footer_links');
if(!footer_links.data('activated_links')){
footer_links.data('activated_links', true);
...
}
...

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

domingo, 11 de enero de 2009

Observadores personalizables en rails

En rails hay unas clases llamadas observadores que sirven para redefinir los métodos que necesitemos en el ciclo de vida de los objetos (after_update, before_create, etc...).

El problema es que en ocasiones con los métodos del ciclo de vida no es suficiente, una situación muy común es redefinir el método after_save, y cada vez que se guarde un objeto comprobar una condición y actuar si es necesario.

Afortunadamente existe un método en ActiveRecord para notificar a los observadores exactamente cuando queramos y de lo que queramos, de la siguiente forma.

En el modelo:

class Post < ActiveRecord::Base
def just_created
notify :manage_task
end
end

En el observador:

class GlobalObserver < ActiveRecord::Observer
observe Post

def manage_task(object)
#do something
end
end

viernes, 2 de enero de 2009

Carga de css dinámica

Es habitual en webs en las que cada usuario tiene una página personal permitirle cambiar la apariencia mediante temas (skins).

Para cargar un css nuevo de forma dinámica se puede usar la siguiente función javascript o algún variante.

function loadCSS(cssFile){
var cssLink=document.createElement("link");
cssLink.setAttribute("rel", "stylesheet");
cssLink.setAttribute("type", "text/css");
cssLink.setAttribute("href", cssFile);
document.getElementsByTagName("head")[0].appendChild(cssLink);
}

Lo que hace esta función es crear un elemento link que apunta a un css y lo inserta en el head de la página.