Publicidad:
La Coctelera

RoR-Lab

Evolución, anécdotas, problemas y soluciones durante el desarrollo de una aplicación web con Ruby on Rails

31 Julio 2007

Variables de clase en el modo "development". (que no le pase a ud.)

Saludos.

Estaba desarrollando una funcionalidad donde necesitaba almacenar información en una variable. Esta información debería ser la misma para todas la instancias de una misma clase. Solución: una variable de clase. Las variables de clase en Ruby (como supongo que saben todos los que leen este blog) se definen con doble arroba (ej. @@myvariable)

Pasa que cuando hacía un 1er request al servidor ponía datos en esta variable y subsequentes request deberían utilizar la información guardada en dicha variable. Algo sencillo que se me convirtió en un infierno; principalmente, por descuido (digo yo, mi hermana lo llama de otra manera).

Digo infierno porque resulta que en el primer request se guardaba información allí, pero en los siguientes requests la información ya no estaba. Le dí vueltas a esto creo yo unas 3 horas. Tres horas de mis precioso y valioso tiempo!, de mi vida!. Bueno no es tanta la tragedia tampoco, además si mi error le puede servir a alguien para algo, valió la pena.

¿Que pasaba?. Como siempre busqué un poco y encontré por fin la solución en uno los hechos básicos del desarrollo en Rails: cuando tienes el servidor en modo "development" cada petición cargará todo nuevamente. Es decir, no se mantendrá el estado de lo que se había puesto anteriormente; entre esas cosas están, como no, las variables de clase!.

Saludos y sí... aunque no lo crean son apenas como 20 almas las que de vez en cuando se pasan por acá.

Aprovecho para proponer una especie de revista sobre Rails (en español) donde los blogers mas conocidos, puedan publicar periódicamente. Eso, creo yo, aportaría mucho a la comunidad.

Tags: tips

servido por Luis Felipe 2 comentarios compártelo

17 Julio 2007

Encarando nuevo proyecto y un problema en los docs de Google Checkout.

Bueno. Hacía como rato que no escribía aquí y la razón es que han pasado una cantidad de cosas que no tenía pensado, pero que una vez se tiene la oportunidad no se pueden dejar pasar. Principalmente los tips o cositas interesantes (creo yo) que he escrito en este blog son principalmente las que me he tocado encarar con el proyecto que estaba desarrollando... si, estaba. Lamentablemente, el proyecto se encuentra parado en este momento. La razón: me contrató una empresa como freelance para un proyecto de e-commerce en Rails; una oportunidad que no podía dejar pasar. Sin embargo, pienso postearles aquí las cosas que aprenda en este nuevo rumbo. Sin mas cháchara (mierda) acá le pongo un pequeño tip que ojalá un día les sirva para algo.

La cosa es sencilla. Resulta que estos peludos de Google (quien mas!), implementaron una cosa parecida a PayPal; se llama Google Checkout. Básicamente la cosa consiste en que si eres un vendedor creas una cuenta como seller y si eres un comprador, pues creas una como buyer. En donde estoy trabajando me pusieron la tarea de implementar toda la API de comunicación con Google Checkout; de manera que cuando un comprador tiene listo el carrito de compras y decide pagar por intermedio de Google Checkout lo pueda hacer; la idea es que tras bambalinas y con puras llamadas XML al API de Google Checkout, sucedan cuestiones como pagar una orden, cancelar una orden, etc. Gracias a Dios (y a dos programadores) existe el google4r-checkout, un plugin que brinda una cantidad de facilidades a un nivel de integración básico, pero que lastimosamente se queda cortico cuando se requiere un nivel de integración mas detallado. Ahí es donde entro yo a ver como hago todo eso.

Una de las cosas básicas es realizar un petición "hello" al servidor de Google Checkout... el debe responder con un "bye". Eso lo explican aquí: http://code.google.com/apis/checkout/developer/index.html#testing_the_api
Les cuento que le boté por lo menos una hora al comandito que explica ahí y simplemente no funcionaba. Me decía que no reconocía el comando 'hello'. Entonces me dije: ese comando está mal escrito, voy a buscar (en Google por supuesto) a ver que infiernos es. Y lo encontré. El commando correcto debería quedar así:

 curl -d "<hello xmlns='http://checkout.google.com/schema/2'>" https://MERCHANT_ID:MERCHANT_KEY@sandbox.google.com/checkout/cws/v2/Merchant/MERCHANT_ID/request
 

En la página oficial de Google Checkout no lo dice, pero es necesario especificarle el "xmls".

Suerte a los veintitantos que de vez en cuando leen este blog.

Tags: google

servido por Luis Felipe 1 comentario compártelo

8 Mayo 2007

Vistas diferentes de un Recurso (Different Views of a Resource)

Siguiendo un poco con esto de REST , me encontré durante el desarrollo con un problema que incluso consulté en los foros, pero nadie supo darme una respuesta.

La cuestión es simple: "un Recurso puede tener diferentes vistas, aún si hay solo un Representación disponible. Por ejemplo, un Recurso tiene una representación XML, pero diferentes clientes podrían ver una porción diferente del mismo XML."

En mi caso el problema se produce por un recurso llamado "producto"; así, la URL "/empresa/:empresa_id/productos" provee una lista de productos.
Este es el Controlador que maneja el Recurso:

 class ProductosController < ApplicationController
 
   # GET /empresa/:empresa_id/productos
   def index
     empresa = Empresa.find(params[:empresa_id])
     render :partial => plantilla, :locals => { 'empresa' => empresa }
   end
 
 end
 

Como se puede ver, el método "index" se va al final a un partial que debe ser un archivo rhtml (o en mi caso un archivo haml), que a la larga será la representación HTML de una lista de productos.

A este URL del recurso se la hace una petición Ajax y el
HTML resultado es desplegado en un cuadrito (<div>) que se
despliega lentamente hacia abajo.

Hasta aquí todo normal.

El problema es que en otra parte del sistema se debe desplegar otro cuadrito con los productos, pero a diferencia del primero donde los productos se muestran como "links" (<a href...), en este los productos deben aparecer como "radio buttons" (<input type="radio"...).

Es claro que la última línea del método "index" (el render), solo me va a dirigir a una vista: ya sea la que muestra los productos como "links" o la que los muestra como "radio buttons".

Escribir otro controlador con un método "index" igual, pero que al final se valla a la otra vista no es para nada DRY.

¿Que hacer entonces?

Buscando como siempre encontré un propuesta de Hao He, en el artículo "Implementing REST Web Services: Best Practices and Guidelines ". Aunque como ahí mismo se advierte: es un estándar informal y podría NO ser RESTful en un sentido estricto.

La solución es: para obtener una vista diferente, un cliente puede poner un parámetro "view" en el query string del URI. Por ejemplo:

 GET http://www.example.com/abc?view=radios
 

De manera que ahora modifiqué el controlador para que tenga en cuenta el parámetro enviado y así determine cual es la vista que el cliente necesita...

class ProductosController < ApplicationController
 
   # GET /empresa/:empresa_id/productos
   def index
     empresa = Empresa.find(params[:empresa_id])
     
     # mostrar la vista correcta de acuerdo al parametro enviado
     plantilla = case params[:view]
                   when 'links'   then 'productos_link'
                   when 'radios' then 'productos_radio'
                   else                     'productos_link'
                 end
     
     render :partial => plantilla, :locals => { 'empresa' => empresa }
   end
 
 end
 

Ahora, cuando se utilicen los helpers para generar la URL al recurso, debe tenerse en cuenta que puede recibir el parámetro "view". Ejemplos:

 productos_path(:empresa_id => empresa.id, :view => 'links')
 productos_path(:empresa_id => empresa.id, :view => 'radios')
 

Saludos y como siempre espero sus comentarios.

Tags: analisis, diseno

servido por Luis Felipe sin comentarios compártelo

27 Abril 2007

Cargar archivo CSV en un arreglo (CSV.to_a)

En la empresa para la cual trabajo, se realizaba un proceso manual muy tedioso que consistía entre otras cosas en cargar dos archivos CSV a Excel y luego hacer una cantidad filtros, cálculos y conversiones (sin comentarios...).
Un día la persona encargada de este proceso, no lo pudo realizar y me lo asignaron a mi con un documento donde se explicaba paso a paso lo que había que realizar.
Como soy alérgico a los proceso manuales, entonces escribí un script en Ruby para que hiciera todo por mi.

Lo primero que necesité fue llenar dos arreglos con unos códigos que estaban formados por campos en unos archivo CSV; un archivo para cada arreglo.

De manera que quiero compartirles el código de un método que carga el contenido de un archivo CSV en un arreglo. Es fácil de entender así que eso se los dejo a ustedes:

 require 'csv'
 
 def CSV.to_a(file, fs = ',', rs =nil, headers = true)
   arr = Array.new
 
   reader = CSV.open(file, 'r', fs)
   reader.shift if headers # quitar los encabezados
   reader.each do |row|
     if block_given?
       res = yield row
     else
       res = row
     end
     arr << res if res
   end
 
   return arr
 end
 

Acá les pongo un par de ejemplo de como se utiliza:

 ordenes_arr = CSV.to_a('ordenesSW.csv','|') do |row| 
   "#{row[0]}-#{row[1]}#{row[2]}#{row[3]}" 
 end
 
 ordenes_ips_arr = CSV.to_a('ordenesIPS.csv') do |row| 
   row[3] unless row[3] == '0'
 end
 
 ordenes_xx_arr = CSV.to_a('ordenesIPS.csv')
 

Saludos,

Tags: tips

servido por Luis Felipe sin comentarios compártelo

18 Abril 2007

Email para confirmar, que además cumple REST.

Uno de los retos en los que me embarqué dentro del proyecto, fue cambiarlo todo para que fuera RESTful. Me compliqué un poco al principio mientras veía como desaparecían las operaciones y comenzaban a aparecer los recursos, pero poco a poco uno se va acostumbrando.

Una de las funcionalidades actuales es que el sistema envía un correo electrónico al email del consumidor cuando éste crea una nueva calificación. En el email se le envía al consumidor un URL que debe utilizar para confirmar su calificación.

Si la aplicación es RESTful, no debería violar este principio: "cualquier petición que modifique el estado del sistema, debe ser enviada por POST y nunca por GET". Mmmmmm, delicado porque la URL que se envía para confirmar la calificación es GET y la tarea en sí implica modificar el estado de una calificación de no confirmada a confirmada.

Se me ocurrió la forma de solucionar esto, pero antes busqué un poco para ver que encontraba. Luego de leer la solución que plantea Elliotte Rusty Harold en "REST Mistake #1: Confirming GETs" ; fue grata mi sorpresa al ver que era la misma que se me había ocurrido; de manera que sin darle mas vueltas, procedí a implementarla.

En resumen, se parte la tarea de confirmar una calificación en dos momentos:
1. el consumidor hace una petición GET desde el enlace enviado a su correo electrónico; en respuesta, el sistema le muestra la información de la calificación que desea confirmar.
2. El consumidor lee la información de la calificación que desea confirmar y oprime un botón que hace una petición POST para cambiar el estado de la calificación de no confirmada a confirmada.
La primera petición que es GET, se enruta a la acción "edit" del recurso y la segunda petición que es POST a la acción "update" del recurso. Cumpliendo así, con REST.

Les aclaro que aunque estamos modificando el estado de confirmación de una calificación, el recurso que utilizo para esta tarea no es el recurso "calificacion" en sí, sino un recurso "virtual" que llamé "confirmación". Lo anterior, es porque el recurso "calificación" debe tener las acciones "edit" y "update" para propósitos muy diferentes a cambiar el estado de la confirmación.

Otra cosa importante, es que en esta tarea de confirmación, nunca utilizo el "id" de la calificación; porque un posible robot, podría utilizar este id, para hacerle cosas nefastas a la aplicación.
De manera que siempre utilizo un llave generada aleatoriamente.

Finalmente el código....

1. El controlador del recurso...

 class ConfirmacionesController < ApplicationController
 
   #POST /confirmaciones/:id
   # :id = llave de confirmación
   def update
     @nueva_calificacion = NuevaCalificacion.find_by_llave_confirmacion(params[:id])
     
     # si NO encuentra una calificación o ya está confirmada, no actualiza nada
     if @nueva_calificacion.nil? or @nueva_calificacion.confirmado
       render(:nothing => true, :status => '404')
     else
       @nueva_calificacion.update_attributes(:confirmado => true)
     end
     
   end
   
   #GET /confirmaciones/:id
   # :id = llave de confirmación
   def edit
     @nueva_calificacion = NuevaCalificacion.find_by_llave_confirmacion(params[:id])
     
     # si NO encuentra una calificación o ya está confirmada, no retorna nada
     if @nueva_calificacion.nil? or @nueva_calificacion.confirmado
       render(:nothing => true, :status => '404')
     end
   end
   
 end
 

2. La vista de la acción "edit", (el método button_to, no lo saqué a un helper como acostumbro para que sea mas fácil mostrarlo acá). Está en haml ...

 Email: 
 = @nueva_calificacion.email
 %br/
 Nombre: 
 = @nueva_calificacion.nombre
 %br/
 %b Empresa: 
 = @nueva_calificacion.producto.empresa.nombre
 %br/
 %b Producto:
 = @nueva_calificacion.producto.nombre
 %br/
 Fecha: 
 = @nueva_calificacion.fecha_creacion
 %br/
 Calificacion: 
 = @nueva_calificacion.valor
 %br/
 Comentario: 
 = @nueva_calificacion.comentario.contenido
 %hr/
 Si, esta es mi calificación y puede ser publicada
 %br/
 = button_to('Aceptar', confirmacion_url(:id => @nueva_calificacion.llave_confirmacion), :method => :put)
 %hr/ 
 

3. La vista de la acción de la acción "update". Está en haml ...

 %h2 ¡Calificación Confirmada!
 %div
   = "El producto #{@nueva_calificacion.producto.nombre} de la empresa #{@nueva_calificacion.producto.empresa.nombre} registra desde este momento su calificación."
   %br/
 

4. Esta es la línea de código para enviar el email. Importante ver el uso del helper "edit_confirmacion_url" ...

Notificador.deliver_solicitud_confirmacion(@nueva_calificacion,edit_confirmacion_url(:id=>@nueva_calificacion.llave_confirmacion))
 

Esto es todo. Saludos y espero sus comentarios.

servido por Luis Felipe 1 comentario compártelo

12 Abril 2007

Varios efectos visuales al tiempo

Esta entrada va a ser corta y puede que hasta muy sencilla. La pongo porque necesité una funcionalidad y no encontré la forma de lograrla documentada en ningún lado (admito que tampoco le dediqué mucho tiempo a dicha búsqueda)...

Al seleccionar un link, se realiza una petición AJAX y al completarse deben suceder estos dos efectos visuales (ver script.aculo.us ): 1. Debía aparecer un div que mostrara el resultado del llamado AJAX, este div debería aparecer lentamente de arriba hacia abajo (conocido como efecto fade); 2. El link seleccionado debería opacarse un poco (conocido como efecto opacity).

Este es el fragmento de código para hacerlo... lo importante por observar es el uso del signo mas (+), para concatenar los efectos...

 <div id="div_id">
 <%= link_to_remote('click aqui', 
                 :update => 'div_actualizar', 
                 :url => { :action => 'metodo_ajax', :id => @empresa.id }, 
                 :complete => visual_effect(:blind_down, 'div_actualizar') + visual_effect(:opacity, 'div_id', {:from => 0.1, :to => 1.0, :duration => 2})
               ) %>
 </div>
 <div id="div_actualizar"></div>
 
 

Saludos...

Tags: tips

servido por Luis Felipe sin comentarios compártelo

3 Abril 2007

La idea es utilizar los helpers...

Las vistas del sistema opté por escribirlas en haml. Encontré en los foros de haml una cosa que me pareció de lo mas interesante. Que era obvia pero no sé si por ser demasiado chobo (novato), no se me había ocurrido.

La cosa consiste en quitar de las vistas las llamadas directas a los helper que reciben una cantidad considerable de parámetros. ¿Quitarlos a donde?; pues a otro helper y ponerle un nombre bien diciente, que realmente al leer el código de las vistas uno entienda que es lo que se está mostrando ahí.

Miren por ejemplo este código que es una parte de una de las vistas que tenía yo antes...

  <% for empresa in @empresas_arr %>
  <li>
  <%= link_to_remote empresa.nombre, :update => 'productos_div', :url => { :action => :mostrar_productos, :id => empresa.id } %>
  </li>
  <% end%>
 

Cuando uno lee eso en una vista no lo entiende fácilmente, es claro que se está iterando un arreglo de empresas, pero entonces se tiene uno que poner a analizar el "link_to_remote", para poder entender que sucede en cada iteración.

Sacar el link_to_remote a un helper

Queda mucho mejor poner el link_to_remote en un helper, algo como:

 <% for empresa in @empresas_arr %>
  <li>
  <%= link_mostrar_productos(empresa) %>
  </li>
  <% end%>

..y el helper quedaría algo así como...

 module CalificarHelper
 
  def link_mostrar_productos(empresa)
    link_to_remote empresa.nombre, 
      :update => 'productos_div', 
      :url => { :action => :mostrar_productos, 
                :id => empresa.id }
  end
 
 end
 

Esto es sin duda algo mucho mas elegante... espero sus comentarios.

PD: para los que ya lo notaron, los ejemplos aunque dije que encontré la técnica en los foros relacionados a haml, están escritos en ERb. (la mayoría de la gente utiliza ERb y la técnica es igualmente aplicable).

Tags: tips

servido por Luis Felipe sin comentarios compártelo

23 Marzo 2007

Eficiencia del captcha

Hace rato que quería poner en consideración el tema del captcha. (El captcha es esto )

Así que vamos al código:

Antes que nada cree un controlador para el captcha, se necesita instalar la gema RMagick (perdí el enlace al blog de donde me basé para esto):

 class CaptchaController < ApplicationController
 
   require_gem 'rmagick'
   
   # generar el captcha y enviarlo al browser
   def mostrar
     # generar los caracteres aleatorios
     captcha_text = Utilidades::GeneradorAleatorio.generar_numero(4)
     
     # generar la capa del texto
     text_layer = Magick::Draw.new
     text_layer.pointsize = 25
     text_layer.fill = '#000'
     text_layer.gravity = Magick::CenterGravity
     
     # obtener las medidas del texto
     medidas = text_layer.get_type_metrics(captcha_text)
     
     # definir lienzo
     canvas = Magick::ImageList.new
     
     # poner imagen de fondo al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#fff'
     }
     
     # poner texto al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#000F'
     }.annotate(text_layer, 0, 0, 0, 0, captcha_text).wave(3, 100)
 
     # opacar la imagen y ponerle ruido
     canvas << Magick::Image.new(medidas.width, medidas.height){
       p = Magick::Pixel.from_color('#fff')
       p.opacity = Magick::MaxRGB/2
       self.background_color = p
     }
 
     # crear image en bytes
     image = canvas.flatten_images.blur_image(1)
     image.format = "JPG"
     captchablob = image.to_blob
 
     # poner texto del captcha en session
     session[:captcha] = captcha_text
     
     # enviar la imagen al browser
     send_data(captchablob,
               :filename => 'captcha.jpg',
               :type => 'image/jpeg',
               :disposition => 'inline')
   end
 end 
 
 

En la primera línea utilizo el generador de aleatorios que ya postee en este mismo blog en otro momento.

Luego de esto en la vista llamo al captcha con una imagen normal, colocándole el src, apuntando al controlador.. algo así como:

 
 <img src="#{url_for(:controller => 'captcha', :action => 'generar', :time => Time.now)}" />
 
 

Esto funciona, pero comencé a cuestionarme sobre el rendimiento de esta alternativa, ya que por cada petición se debía volver a crear la imagen... eso me pareció como demasiado pesado.
El captcha que necesito tiene solamente 4 dígitos... va desde el 0000 hasta el 9999. Entonces, mas bien me dije... que tal si pre-generamos las imágenes posibles, las guardamos en el directorio de imágenes de la aplicación y en lugar de construir la imagen en cada petición, mas bien la leemos del disco y la enviamos... entonces adicioné una acción nueva al controlador... y quedó así (una consideración importante es que el nombre del archivo de imagen
concuerda con la imagen del número, entonces allí en el directorio
encontraremos archivos como: 0000.jpg, 2345.jpg, etc.

) :

 class CaptchaController < ApplicationController
   def mostrar
     # generar los caracteres aleatorios
     captcha_text = Utilidades::GeneradorAleatorio.generar_numero(4)
     imagen = Magick::Image::read("#{RAILS_ROOT}/public/images/captcha/#{captcha_text}.jpg").first
     
     # poner texto del captcha en session
     session[:captcha] = captcha_text
     
     # enviar la imagen al browser
     send_data(imagen.to_blob,
               :filename => 'captcha.jpg',
               :type => 'image/jpeg',
               :disposition => 'inline')
     
   end
 end
 
 

Esto además de evitar el "peso" de crear una imagen en cada petición, evita que en el servidor de producción se tenga instalada la gema Rmagick. En contraste en el directorio public/images/captcha quedaron algo así como 10 MB de imágenes.

Este es el script en Ruby utilizado para pre-generar las imágenes, obviamente se reutiliza el mismo código que había en la acción original del controlador:

 require_gem 'rmagick'
   
   # generar la imagen del número y guardarlo en el disco
   def generar_imagen(texto)
     # generar la capa del texto
     text_layer = Magick::Draw.new
     text_layer.pointsize = 25
     text_layer.fill = '#000'
     text_layer.gravity = Magick::CenterGravity
     
     # obtener las medidas del texto
     medidas = text_layer.get_type_metrics(texto)
     
     # definir lienzo
     canvas = Magick::ImageList.new
     
     # poner imagen de fondo al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#fff'
     }
     
     # poner texto al canvas
     canvas << Magick::Image.new(medidas.width, medidas.height){
       self.background_color = '#000F'
     }.annotate(text_layer, 0, 0, 0, 0, texto).wave(3, 100)
 
     # opacar la imagen y ponerle ruido
     canvas << Magick::Image.new(medidas.width, medidas.height){
       p = Magick::Pixel.from_color('#fff')
       p.opacity = Magick::MaxRGB/2
       self.background_color = p
     }
 
     # crear image en bytes
     image = canvas.flatten_images.blur_image(1)
 
     # guardar imagen en archivo
     image.write("#{texto}.jpg")
   end
 
 0.upto(999) do |x|
   texto = x.to_s
   texto = "000" << texto if x < 10
   texto = "00" << texto if x >= 10 and x < 100
   texto = "0" << texto if x >= 100 and x < 1000
   generar_imagen(texto)
 end
 
 

Como siempre, saludos y espero sus comentarios...

servido por Luis Felipe 2 comentarios compártelo


Sobre mí

Actualmente me encuentro desarrollando una aplicación web que espero tener en producción para la mitad del 2007. La aplicación está desarrollada utilizando el framework Ruby on Rails. (RoR) Toda la evolución del sistema va a ser documentada en este blog con el ánimo de recibir retroalimentación o bien ser útil para otros desarrollos.

Fotos

Luis Felipe Hurtado Campuzano todavía no ha subido ninguna foto.

¡Anímale a hacerlo!

Buscar

suscríbete

Selecciona el agregador que utilices para suscribirte a este blog (también puedes obtener la URL de los feeds):

¿Qué es esto?

Crea tu blog gratis en La Coctelera