Trabe ya no escribe aquí. Puedes encontrarnos en nuestra publicación en Medium: medium.com/trabe.

4Trabes Historias de una empresa en 100 metros cuadrados

El blog de Trabe Soluciones

Sitemaps autogenerados con Mephisto

|

Estos días hemos decidido que nos interesa que mephisto haga el solito sus deberes. El primer paso es que genere su sitemap, para sugerirle educadamente a Google que rutas quiere que sean indexadas. El segundo paso será entrenarlo para que notifique a los technorati y demás familia por si solo cuando haya cambios.

En teoría hacer lo primero es muy sencillo, porque hay un plugin que se supone que ya lo hace. Nada más fácil que esto…en teoria. Pero en la práctica esto no es del todo asi.

La buena noticia es que el plugin si que genera un sitemap, pero la mala es que simplemente con leerlo por encima nos damos cuenta de que este sitemap tiene algunos errores. Asi que vamos a arreglarlo para que sea algo más correcto. No buscamos una solución general, ni hacer un nuevo plugin, solo que funcione bien para nuestro caso.

El primer gran error, y el más garrafal en mi opinión, es que genera entradas en el sitemap para los articulos que aun no están publicados. Es decir, que todo lo que esté como draft va a aparecer igual en el sitemap, por supuesto con una ruta inválida. Localizar el código que hace esto es sencillo, está en el fichero index.rxml en la ruta /app/views/google_site_map/ del plugin. Echemos un vistazo a ver que puede estar pasando:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xml.instruct! :xml, :version=> '1.0', :encoding => 'UTF-8'

xml.urlset( :xmlns => 'http://www.google.com/schemas/sitemap/0.84') do
  render :partial => "page", :locals => {
    :xm => xml,
    :loc => MephistoGoogleSiteMap.site_uri,
    :changefreq => MephistoGoogleSiteMap.home_frequency,
    :priority => MephistoGoogleSiteMap.home_priority,
    :lastmod => MephistoGoogleSiteMap.lastmod(@last_article)
  }
  for section in @sections do
    for article in section.articles do
      render :partial => "page", :locals => {
        :xm => xml,
        :loc => MephistoGoogleSiteMap.location(@site, article.to_liquid, section),
        :changefreq => MephistoGoogleSiteMap.change_frequency(section),
        :priority => MephistoGoogleSiteMap.article_priority,
        :lastmod => MephistoGoogleSiteMap.lastmod(article)
      }
    end
  end
end

El error parece evidente: para cada sección se iteran los artículos de la misma y para cada uno de ellos se pinta la entrada correspondiente en el sitemap. Buen intento, pero sería mejor hacerlo solo para aquellos artículos que estén publicados. Asi evitariamos meter los drafts en el sitemap. Solventar este pequeño problema es fácil. Con un if vamos sobrados:

1
2
3
4
5
6
7
8
9
10
11
12
13
for section in @sections do
  for article in section.articles do
    if article.published?
      render :partial => "page", :locals => {
        :xm => xml,
        :loc => MephistoGoogleSiteMap.location(@site, article.to_liquid, section),
        :changefreq => MephistoGoogleSiteMap.change_frequency(section),
        :priority => MephistoGoogleSiteMap.article_priority,
        :lastmod => MephistoGoogleSiteMap.lastmod(article)
      }
    end
  end
end

Podría escribirse de forma más elegante, pero dificilmente más sencillo. Bien, ya no nos salen los drafts, primer break point superado. Vamos a por el partido. El segundo problema que he detectado es que si un artículo está contenido en varias secciones – pongamos que está publicado en n secciones-, la entrada correspondiente del sitemap va a aparecer repetida n veces. Esto es de una utilidad discutible, asi que será mejor arreglarlo. Podriamos hacer una comprobación de si ya hemos generado una entrada o no…pero parece que esto se pone complejo, y no debería serlo, ¿no?…a lo mejor es que este bucle no está muy bien pensado, ¿por qué razón estamos iterando las secciones ?¿Para asignar la prioridad?¿Por qué existe la posibilidad de generar entradas de distinta prioridad para la misma url?…aqui veo lagunas no se…creo que se puede hacer muuucho más simple y además que funcione bien. Comentar que el tema de las secciones no-blog lo voy a eliminar, porque nosotros no lo usamos . Vamos a empezar por el controlador. No necesitamos iterar las secciones, con un listado de articulos ya nos llega, y además podemos poner la condición de que esté publicado directamente en el find, con lo que quitamos lógica de negocio de la vista, por aquello del estructurar. Asi, el método index de nuestro controlador google_site_map_controller.rb pasa de ser algo asi:

1
2
3
4
5
def index
  headers['Content-Type'] = 'text/xml; charset=utf-8'
  @sections = site.sections
  @last_article = Article.find_by_date(:limit => 1).first
end

a convertirse en algo como esto:

1
2
3
4
5
def index
  headers['Content-Type'] = 'text/xml; charset=utf-8'
  @articles = Article.find(:all,:conditions=>'published_at is not null')
  @last_article = Article.find_by_date(:limit => 1).first
end

Simplificamos un poco la clase del lib, eliminando algun método y simplificando algún otro:

1
2
3
4
5
6
7
8
9
  def get_frequency_for_article
  self.blog_frequency
end

def location(site, article)
  filters = FiltersProxy.instance
  article.instance_variable_set("@site", site)
  self.site_uri +  article.url
end

Como hemos comentado más arriba, hemos eliminado la particularidad de las secciones no-blog. Asi las cosas, el bucle de la vista que genera el sitemap, pasa a ser algo como esto:

1
2
3
4
5
6
7
8
9
for article in @articles do
  render :partial => "page", :locals => {
    :xm => xml,
    :loc => MephistoGoogleSiteMap.location(@site, article.to_liquid),
    :changefreq => MephistoGoogleSiteMap.get_frequency_for_article,
    :priority => MephistoGoogleSiteMap.article_priority,
    :lastmod => MephistoGoogleSiteMap.lastmod(article)
  }
end

Ya no tenemos los borradores en el sitemap ni entradas duplicadas, con lo que google estará más contento y más predispuesto a ser amiguito de nuestra página ;)

Pero aun no podemos cantar victoria: el formato en que se generan las fechas no es acorde a lo definido por el W3C. Esto es fácil de subsanar: en el init.rb del plugin se encuentra definido el simbolo :w3. Solo hay que tocar esa definición para añadirle información sobre vuestra zona horaria:

1
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(:w3 => '%Y-%m-%dT%H:%M:%S+01:00')

Y ahora si que es posible que tengamos un sitemap decentillo.


Lo sentimos, pero los comentarios están cerrados