Logo de La Coctelera

Categoría: Snippets y Trucos

Generating static maps with attachment_fu and static-gmaps gem

18 Ago 08

Google Maps is, may be, the coolest service for integrating maps in your web applications: it offers a powerful and easy API with no limit usage.

The problem is that it loads quite Javascript that has to connect to the main Google Maps page in order to draw our map. In order to avoid that we can use static Google Maps, it is, a picture (generated in real time) of the map we want to visualize. The problem with this is that you can't interact with the map (because it is an image) and that there is a limitation per day. But may be you find it useful in a particular moment.

For example, in iwannagothere.net we have a small map in every place that shows the location of that city:

In that situation we can change the map because the map is not for navigating along the city. For that you have a map in each item.

In Ruby on Rails, the programming framework of iwannagothere.net there is a gem called static-gmaps that does the hard work for you. What's more, we have found the way to integrate this maps with attachment_fu, in order you save a copy of the map in your filesystem and then avoid the day limitation usage.

Basically we have an attachment_fu kind of model (nothing special here):

class PlaceMap < ActiveRecord::Base
   belongs_to :place
 
   has_attachment  :content_type => :image, 
                   :storage => :file_system,
                   :processor=> :rmagick,
                   :path_prefix => 'public/userfiles/place_maps', 
                   :max_size => 1.megabyte
 
   validates_as_attachment  
 end
 

The we generate the static map instance:

	
 map = StaticGmaps::Map.new :center   => [ place.lat, place.long ],
                             :zoom     => 12,
                             :size     => [ 334, 144 ],
                             :key      => APP['gmkey']  
 

And save it in our PlaceMap model:

 PlaceMap.create(:uploaded_data => (open(map.url)), :place_id => place.id, :content_type => 'image/gif')
 

The problem here is that we are not uploading a file in the usual way: we are trying to save a raw of data. Attachment_fu doesn't know how to do that, so we have to apply a little patch:

 module Technoweenie
   module AttachmentFu
     module InstanceMethods
 
       def uploaded_data=(file_data)
         return nil if file_data.nil? || file_data.size == 0
         self.content_type = file_data.content_type
         self.filename     = Time.now.to_i.to_s+'.gif'
         if file_data.is_a?(StringIO)
           file_data.rewind
           self.temp_data = file_data.read
         else
           self.temp_path = file_data
         end
       end          
 
     end
   end
 end
 

Here we are giving a random filename, generated from the current time.

And that's all, really easy :)

2 comentarios

Comprobar si una relacíon contiene un elemento

9 Jun 08

Muchas veces cuando trabajamos con ActiveRecord nos olvidamos / ignoramos qué hace por debajo.

Un claro ejemplo lo encontramos cuando tenemos una relación :has_many y queremos comprobar si un elemento está incluída en dicha relación.

Por ejemplo:

 class Place < ActiveRecord::Base
   has_many :items
 end
 
 class Item < ActiveRecord::Base
   belongs_to :place
 end
 

Seguro que más de una vez, para comprobar que un place contiene un ítem hemos utilizado el "rubysta" include?:

 place.items.include?(item)
 

Pensándolo fríamente 0.2 segundos nos damos cuenta de que estamos cargando un array con todos los elementos de una relación, que pueden ser 10 o pueden ser 1.000.000. Y ya sabemos lo generoso que es Ruby con la memoria que toma y que nunca deja ir.

Un truco para evitar que esto suceda sin dejar de utilizar include? es definir dicho método dentro de la relación tal que así:

 class Place < ActiveRecord::Base
   has_many :items do 
     def include?(item)
       count(:conditions => ["item_id = ?", item.id]) > 0
     end
   end
 end
 

Es decir, resolvemos el problema con un simple COUNT de Mysql.

¿Alguien se anima a hacer un plugin? Creo que sería bastante inmediato para cuando se cumplen las convenciones y sabemos que la clave foránea de la relación es singular del nombre de la relación_id. O quizá ni eso, si encontramos la forma de preguntarle a ActiveRecord cuál es la clave foránea de una relación.

2 comentarios

Visualizar en el log de producción las querys lentas

5 Jun 08

Cuando tienes una aplicación en producción es más que necesario controlar los tiempos de las peticiones más lentas.

Sin embargo muchas veces con el log de Rails no es suficiente, y hay que alcanzar un mayor nivel de detalle.

El log en modo debug nos da todo el detalle que necesitamos, pero mostrando demasiada información (¿de qué nos sirve saber que un SELECT sencillo tarda 0.001 segundos?).

Pero si en ese log pudiéramos filtrar las consultas SQL más lentas sí que tendríamos información de consultas lentas asociadas a cada petición al servidor.

Gracias al plugin query trace y a una pequeña modificación podemos mostrar qué consultas tardan más de 1 segundo:

 module ActiveRecord
   module ConnectionAdapters
     class AbstractAdapter
       def log_info(sql, name, runtime)
         return unless @logger
         @logger.info(
           format_log_entry(
             "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
             sql.gsub(/ +/, " ")
           )
         ) if (RAILS_ENV == 'production' && runtime > 1.0) || RAILS_ENV != 'production'
       end
     end
   end
 end
 

Este código lo he probado en Rails 1.2.x, no sé si funcionará en otras versiones.

1 comentario

Diferencias colorizadas en SVN y GIT

31 May 08

Si trabajáis con control de versiones una de las operaciones más habituales es ver las modificaciones de un fichero. Eso en Subversion se hace con svn diff.

Sin embargo yo utilizo un script que creo que me pasó porras para que me muestre en colores las modificaciones.

El script es este:


 #!/usr/bin/env ruby
 
 `svn diff #{ARGV.join(' ')}`.each do |line|
   puts( if line =~ /^\+(.*)$/
         "\e[32m#{$&}\e[0m"
         elsif line =~ /^-(.*)$/
           "\e[31m#{$&}\e[0m"
         else
           line
         end
       )
 end
 

Con darle permisos de ejecución ya podéis tener un svndiff por ejemplo.

No pongo el equivalente en git porque es bastante inmediato.

Tags:
6 comentarios

Sirviendo feeds cacheados con Nginx directamente

16 Ene 08

Reconozco que me ha constado dios y ayuda dar con la regla adecuada, así que por eso me he decidido a postearla.

El problema es el siguiente: en unvlog.com (sí, ese proyecto del que no he hablado) somos muy Rails y tenemos para todos los recursos que tiene sentido una ruta que responde a un formato u otro según se le solicite pero con esta forma:

En concreto, esta URL corresponde al feed de un blog, feed que está cacheado en disco gracias a la caché de página de Rails. Pues bien, la idea es que el servidor web sirva directamente esta caché, así que una regla podría ser:

   if (-f /cache/$request_filename) {
           rewrite (.*) /cache/$1 break;
   }
 

(Esto, asumiendo que tenemos la caché en public/cache)

El problema es que esta regla no funciona, y creo que se debe a que blat.atom lleva un '.' y no es capaz de asignar la URL a la variable $request_filename.

Al final ha sido tan fácil como utilizar otra variable que he encontrado en la parte rusa del wiki, $request_uri:

   if (-f $document_root/cache/$request_uri) {
           rewrite ^/(.*).atom$ /cache/$1.atom break;
   }
 

Con esto ha funcionado perfectamente y ya tenemos cacheados y servidos por Nginx los feeds en unvlog.com.

Tags: ,
1 comentario

Testeando helpers

12 Ago 07

El otro día, buscando información sobre cómo testear helpers en Rails di con este post: Test your helpers, en el que el autor habla de un plugin desarrollado por él mismo que facilita sobremanera la labor.

El plugin se llama helper_test y es muy sencillo de utilizar:

  1. descargar
  2. editar tests/test_helper.rb e incluír la línea: require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
  3. utilizar el generador que incluye: ./script/generate helper_test Foo

El test generado tendrá este aspecto:


   require File.dirname(__FILE__) + '/../../test_helper'
 
   class FooHelperTest < HelperTestCase
 
     include FooHelper
 
     #fixtures :users, :articles
 
     def setup
       super
     end
 
   end
 

A destacar que podemos utilizar fixtures.

Y la forma de proceder también es muy sencilla: creamos un test y en su interior podemos invocar al helper como si de un método más se tratara y comparar el resultado obtenido con los valores esperados. Si el helper tiene argumentos, ampliaremos el abanico de pruebas.

Una pequeña reflexión

La verdad es que no he podido pensar mucho sobre la efectividad de este plugin ni tampoco tengo experiencia utilizándolo (de hecho los desarrolladores de The-Shaker estábamos algo preocupados sobre el vacío que teníamos en los helpers de la aplicación, pues no tienen tests). En principio y echando un pequeño vistazo a los helpers que tenemos, parece que sí que se van a poder testear todos utilizando este plugin.

Sin embargo, hay que notar dos cosas:

La primera es que si en tu helper has utilizado una variable de instancia (confiando en que en la vista donde vas a utilizar dicho helper, la variable ya esté instanciada y con valor), debes de asignar una variable de igual nombre antes de invocar al helper en el test. Por ejemplo:


 def available_tabs
   tabs =  "<ul>\n"
   tabs << " <li id=\"tab_blog\">" + link_to(_("Blog"), :controller => 'posts',
            :action => 'index', :blog_nicetitle => @blog.nicetitle) + "</li>\n"
   tabs << "</ul>\n"
 end

Y el test:


 def test_avaible_tabs
   @blog = blogs(:el_blog_de_quentin)
   tabs = available_tabs
   assert_equal tabs, '....'
 end

Y la segunda es más bien una sugerencia: muchas veces los helpers generan HTML, y todos sabemos lo coñazo que puede ser comprobar la estructura del mismo, y las bondades del assert_select. Pues bien, se puede hacer un hack no muy elegante, pero bastante práctico para poder utilizar assert_select con el valor obtenido por un helper. Basta con editar el fichero helper_testcase.rb e incluír la línea:


   @response   = ActionController::TestResponse.new
 

dentro de la función setup. Y ahora, con que asignemos el helper a @response.body ya lo tenemos. Veamos un ejemplo:


 def test_avaible_tabs
   @blog = blogs(:el_blog_de_quentin)
   @response.body = available_tabs
   assert_select 'ul'
 end

Es sucio, poco semántico, y muy DRY, porque lo tienes que asignar tras cada invocación al helper, pero parece que de momento no hay otra forma. ¿Alguien se atreve a redefinir?

sin comentarios

Y antes fue la línea de comandos...

30 Jul 07

La línea de comandos (¿esa gran desconocida?), es una de las mejores herramientas que tenemos a mano los que desarrollamos en entorno UNIX/Linux. Por eso, posts como este, inspiran.

Y es que seguro que no todo lo que cuenta lo desconoces, pero tiene dos o tres perlas muy buenas.

Aquí van las que más me han gustado:

Repetir un comando con sudo:


 	$ vim /etc/hosts
 	$ sudo !!
 	sudo vim /etc/hosts
 	Password:
 

Utilizar el history para lanzar comandos:


 $ history | head
     6  vim config/deploy.rb 
     7  vim config/deploy
     8  vim config/deploy.rb 
     9  cap club_deploy
    10  ssh bellini
    11  cd Proyectos/new-shaker-club/
    12  vim appserver/etc/logrotate.d/the-shaker
 $ !10 # lanza un ssh a bellini
 

Alias de irb:


 	alias irb='irb --readline -r irb/completion -rubygems' # use readline, completion and require rubygems by default for irb
 

Hay muchísimos más en el post que vale la pena repasar.

Y aquí algunos de mi cosecha:

Alias para resetear caché de resolución de nombres (ideal para cuando modificas el /etc/hosts):


 	alias dns='sudo lookupd -flushcache'
 

Alias para ver los cambios en Subversion del directorio actual:


 	alias stm='svn st | grep ^M'
 

Seguro que tú también utilizas alguno que otro, ¡así que ahí tienes los comentarios para compartirlo con los demás!

Actualización: y como remate a este post os dejo mi .irbrc en este pastie.

4 comentarios

Cosas que hago al empezar un nuevo proyecto en Rails

9 Jun 07

A lo largo de todo este tiempo que llevo como desarrollador de Rails he cogido una serie de manías y hábitos, a la vez que he descubierto una serie de herramientas y plugins que me han parecido imprescindibles, y que incluso podrían venir incorporadas de serie en Rails.

Así que tras el clásico rails vaporware_project hago lo siguiente:

Instalar el plugin annotate_models

Se trata de un plugin que te copia un listado de atributos de la clase como comentarios en los ficheros del modelo y de las fixturas mediante un simple rake annotate_models.

El resultado es algo como esto:


 # == Schema Information
 # Schema version: 4
 #
 # Table name: sources
 #
 #  id          :integer(11)   not null, primary key
 #  url         :string(255)   default(), not null
 #  feed_type   :string(255)   default(), not null
 #  title       :string(255)   default(), not null
 #  description :text          
 #  created_at  :datetime      not null
 #  updated_at  :datetime      not null
 #
 class Source < ActiveRecord::Base
 

Más información: http://agilewebdevelopment.com/plugins/annotate_models

Instalar el plugin test_fixtures

Este plugin, desarrollado por Ernesto Jiménez, añade dinámicamente un test de las fixturas de cada uno de los modelos.

Más información: http://www.lacoctelera.com/ernesto-jimenez/post/2007/04/19/plugins-mi-propia-cosecha

Instalar el plugin memory_test_fix

Se trata de un plugin que permite que los tests se ejecuten contra una base de datos cargada en memoria, lo cuál hace que vayan infinitamente más rápidos, algo muy recomendable si su número crece y crece.

Más información: http://agilewebdevelopment.com/plugins/memory_test_fix

Tanto este plugin como el anterior son ideales si vas a empezar un proyecto siguiendo la metodología TDD

Limpiar los ficheros database.yml y environment.rb

Esto es más una manía que otra cosa, pero ambos ficheros vienen llenos de comentarios (muy instructivos y útiles, la verdad), pero que hace que ocupen infinitamente más de lo que deberían de ocupar.

Por ejemplo, mi database.yml es así:


 development:
   adapter: mysql
   database: vapor_development
   username: user
   password: pass
   host: localhost
 test:
   adapter: sqlite3
   database: ":memory:"
 

Y mi environment.rb es así:


 RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION
 require File.join(File.dirname(__FILE__), 'boot')
 Rails::Initializer.run do |config|
 end
 

Utilizar los generadores de Rails

Los generadores de Rails son realmente útiles para crear las plantillas de los ficheros que vamos a utilizar: son mucho más limpios que el scaffold porque crean los ficheros vacíos, pero ya crean fixturas, controladores, tests, etcétera.

Especialmente recomendable el nuevo script/generate resource item, que nos creará nuestro modelo y nuestro controlador como un recursos, creando también las rutas asociadas.

Copio el script rsql en mi carpeta de scripts

El script rsql nos lo enseñó Juan Lupión, y si le dieran un céntimo por cada vez que lo utilizamos ya sería rico.

Se trata de un script de consola que toma como argumento el entorno (cogiendo por defecto development si no indicamos nada) y abre un cliente de SQL con los datos que lee del fichero config/database.yml, con lo cuál en lugar de un mysql -u user -p password -h host databasename, basta con hacer un script/rsql production para entrar en la base de datos de producción, por ejemplo.

El script no sé de donde es ni que licencia tiene, pero podéis descargar de aquí una versión mejorada por el mismo Juan.

The end

Y eso es todo, hay cosas más personales y hay cosas que son un must do, pero seguro que os resultan útiles.

¿Y tú tienes algún consejo?

6 comentarios