31 Julio 2007
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.
servido por Luis Felipe
2 comentarios
compártelo
17 Julio 2007
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.
servido por Luis Felipe
1 comentario
compártelo
8 Mayo 2007
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.
servido por Luis Felipe
sin comentarios
compártelo
27 Abril 2007
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,
servido por Luis Felipe
sin comentarios
compártelo
18 Abril 2007
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
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...
servido por Luis Felipe
sin comentarios
compártelo
3 Abril 2007
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).
servido por Luis Felipe
sin comentarios
compártelo
23 Marzo 2007
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