RoR-Lab Evolución, anécdotas, problemas y soluciones durante el desarrollo de una aplicación web con Ruby on Rails 2007-07-31T23:31:59+00:00
This is an Atom syndication feed. It is intended to be viewed in a news aggregator or syndicated to another site. Please visit the Atom Project for more information.
Tecnología the-shaker: that blog/flickr/multimedia-aggregator kind of thing RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/07/31/variables-clase-el-modo-development-que-le-pase-a Variables de clase en el modo "development". (que no le pase a ud.) 2007-07-31T23:31:59+00:00 2007-11-06T08:05:59+00:00 <p>Saludos.</p> <p>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)</p> <p>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).</p> <p>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.</p> <p>¿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!.</p> <p>Saludos y sí... aunque no lo crean son apenas como 20 almas las que de vez en cuando se pasan por acá.</p> <p>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.</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/07/17/encarando-nuevo-proyecto-y-problema-los-docs-google Encarando nuevo proyecto y un problema en los docs de Google Checkout. 2007-07-17T04:47:52+00:00 2007-11-06T08:03:41+00:00 <p>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.</p> <p>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.</p> <p>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<br /> 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í:</p> <pre> curl -d "&lt;hello xmlns='http://checkout.google.com/schema/2'&gt;" https://MERCHANT_ID:MERCHANT_KEY@sandbox.google.com/checkout/cws/v2/Merchant/MERCHANT_ID/request </pre> <p>En la página oficial de Google Checkout no lo dice, pero es necesario especificarle el "xmls".</p> <p>Suerte a los veintitantos que de vez en cuando leen este blog.</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/05/08/vistas-diferentes-un-recurso-different-views-of-resource- Vistas diferentes de un Recurso (Different Views of a Resource) 2007-05-08T18:57:05+00:00 2007-11-06T07:50:45+00:00 <p>Siguiendo un poco con esto de <a href="http://es.wikipedia.org/wiki/REST" title="http://es.wikipedia.org/wiki/REST" id="link_0">REST</a> , me encontré durante el desarrollo con un problema que incluso consulté en los foros, pero nadie supo darme una respuesta. </p> <p>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."</p> <p>En mi caso el problema se produce por un recurso llamado "producto"; así, la URL "/empresa/:empresa_id/productos" provee una lista de productos.<br /> Este es el Controlador que maneja el Recurso:</p> <pre> class ProductosController &lt; ApplicationController # GET /empresa/:empresa_id/productos def index empresa = Empresa.find(params[:empresa_id]) render :partial =&gt; plantilla, :locals =&gt; { 'empresa' =&gt; empresa } end end </pre> <p>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.</p> <p>A este URL del recurso se la hace una petición Ajax y el<br /> HTML resultado es desplegado en un cuadrito (&lt;div&gt;) que se<br /> despliega lentamente hacia abajo.</p> <p>Hasta aquí todo normal.</p> <p>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" (&lt;a href...), en este los productos deben aparecer como "radio buttons" (&lt;input type="radio"...).</p> <p>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".</p> <p>Escribir otro controlador con un método "index" igual, pero que al final se valla a la otra vista no es para nada DRY.</p> <p><strong>¿Que hacer entonces?</strong></p> <p>Buscando como siempre encontré un propuesta de Hao He, en el artículo "<a href="http://www.xml.com/pub/a/2004/08/11/rest.html" title="http://www.xml.com/pub/a/2004/08/11/rest.html" id="link_1">Implementing REST Web Services: Best Practices and Guidelines</a> ". Aunque como ahí mismo se advierte: es un estándar informal y podría NO ser RESTful en un sentido estricto.</p> <p>La solución es: <strong>para obtener una vista diferente, un cliente puede poner un parámetro "view" en el query string del URI</strong>. Por ejemplo:</p> <pre> GET http://www.example.com/abc?view=radios </pre> <p>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...</p> <pre>class ProductosController &lt; 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 =&gt; plantilla, :locals =&gt; { 'empresa' =&gt; empresa } end end </pre> <p>Ahora, cuando se utilicen los helpers para generar la URL al recurso, debe tenerse en cuenta que puede recibir el parámetro "view". Ejemplos:</p> <pre> productos_path(:empresa_id =&gt; empresa.id, :view =&gt; 'links') productos_path(:empresa_id =&gt; empresa.id, :view =&gt; 'radios') </pre> <p>Saludos y como siempre espero sus comentarios.</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/04/27/cargar-archivo-csv-un-arreglo-csv-to_a- Cargar archivo CSV en un arreglo (CSV.to_a) 2007-04-27T23:42:25+00:00 2007-11-06T07:49:10+00:00 <p>En la empresa para la cual trabajo, se realizaba un proceso manual muy tedioso que consistía entre otras cosas en cargar dos archivos <a href="http://es.wikipedia.org/wiki/CSV" title="http://es.wikipedia.org/wiki/CSV" id="link_0">CSV</a> a Excel y luego hacer una cantidad filtros, cálculos y conversiones (sin comentarios...).<br /> 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.<br /> Como soy alérgico a los proceso manuales, entonces escribí un script en Ruby para que hiciera todo por mi.</p> <p>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.</p> <p>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:</p> <pre> 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 &lt;&lt; res if res end return arr end </pre> <p>Acá les pongo un par de ejemplo de como se utiliza:</p> <pre> 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') </pre> <p>Saludos,</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/04/18/email-confirmar-ademas-cumple-rest- Email para confirmar, que además cumple REST. 2007-04-18T22:03:58+00:00 2007-11-06T07:47:25+00:00 <p>Uno de los retos en los que me embarqué dentro del proyecto, fue cambiarlo todo para que fuera <a href="http://es.wikipedia.org/wiki/REST" title="http://es.wikipedia.org/wiki/REST" id="link_0">REST</a>ful. Me compliqué un poco al principio mientras veía como desaparecían las <em>operaciones</em> y comenzaban a aparecer los <em>recursos</em>, pero poco a poco uno se va acostumbrando.</p> <p>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.</p> <p>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 n<em>o confirmada</em> a <em>confirmada</em>.</p> <p>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 <a href="http://cafe.elharo.com/web/rest-mistake-1-confirming-gets/" title="http://cafe.elharo.com/web/rest-mistake-1-confirming-gets/" id="link_1">"REST Mistake #1: Confirming GETs"</a> ; 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.</p> <p>En resumen, se parte la tarea de confirmar una calificación en dos momentos:<br /> 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.<br /> 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 <em>no confirmada</em> a <em>confirmada</em>.<br /> 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.</p> <p>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.</p> <p>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.<br /> De manera que siempre utilizo un llave generada aleatoriamente.</p> <p>Finalmente el código....</p> <p>1. El controlador del recurso...</p> <pre> class ConfirmacionesController &lt; 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 =&gt; true, :status =&gt; '404') else @nueva_calificacion.update_attributes(:confirmado =&gt; 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 =&gt; true, :status =&gt; '404') end end end </pre> <p>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 ...</p> <pre> 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 =&gt; @nueva_calificacion.llave_confirmacion), :method =&gt; :put) %hr/ </pre> <p>3. La vista de la acción de la acción "update". Está en haml ...</p> <pre> %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/ </pre> <p>4. Esta es la línea de código para enviar el email. Importante ver el uso del helper "edit_confirmacion_url" ...</p> <pre>Notificador.deliver_solicitud_confirmacion(@nueva_calificacion,edit_confirmacion_url(:id=&gt;@nueva_calificacion.llave_confirmacion)) </pre> <p>Esto es todo. Saludos y espero sus comentarios.</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/04/12/varios-efectos-visuales-al-tiempo Varios efectos visuales al tiempo 2007-04-12T01:10:17+00:00 2007-11-06T07:45:58+00:00 <p>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)...</p> <p>Al seleccionar un link, se realiza una petición AJAX y al completarse deben suceder estos dos efectos visuales (ver <a href="http://scipt.aculo.us" title="http://scipt.aculo.us" id="link_0">script.aculo.us</a> ): 1. Debía aparecer un<strong> div</strong> que mostrara el resultado del llamado AJAX, este <strong>div</strong> debería aparecer lentamente de arriba hacia abajo (conocido como efecto <em>fade</em>); 2. El link seleccionado debería opacarse un poco (conocido como efecto <em>opacity</em>).</p> <p>Este es el fragmento de código para hacerlo... lo importante por observar es el uso del signo mas (+), para concatenar los efectos...</p> <pre> &lt;div id="div_id"&gt; &lt;%= link_to_remote('click aqui', :update =&gt; 'div_actualizar', :url =&gt; { :action =&gt; 'metodo_ajax', :id =&gt; @empresa.id }, :complete =&gt; visual_effect(:blind_down, 'div_actualizar') + visual_effect(:opacity, 'div_id', {:from =&gt; 0.1, :to =&gt; 1.0, :duration =&gt; 2}) ) %&gt; &lt;/div&gt; &lt;div id="div_actualizar"&gt;&lt;/div&gt; </pre> <p>Saludos...</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/04/03/la-idea-es-utilizar-helpers- La idea es utilizar los helpers... 2007-04-03T23:28:53+00:00 2007-11-06T07:44:32+00:00 <p>Las vistas del sistema opté por escribirlas en <a href="http://haml.hamptoncatlin.com/" title="http://haml.hamptoncatlin.com/" id="link_0">haml</a>. 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. </p> <p>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í.</p> <p>Miren por ejemplo este código que es una parte de una de las vistas que tenía yo antes...</p> <pre> &lt;% for empresa in @empresas_arr %&gt; &lt;li&gt; &lt;%= link_to_remote empresa.nombre, :update =&gt; 'productos_div', :url =&gt; { :action =&gt; :mostrar_productos, :id =&gt; empresa.id } %&gt; &lt;/li&gt; &lt;% end%&gt; </pre> <p>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.</p> <p><strong>Sacar el link_to_remote a un helper</strong></p> <p>Queda mucho mejor poner el link_to_remote en un helper, algo como:</p> <pre> &lt;% for empresa in @empresas_arr %&gt; &lt;li&gt; &lt;%= link_mostrar_productos(empresa) %&gt; &lt;/li&gt; &lt;% end%&gt;</pre> <p>..y el helper quedaría algo así como...</p> <pre> module CalificarHelper def link_mostrar_productos(empresa) link_to_remote empresa.nombre, :update =&gt; 'productos_div', :url =&gt; { :action =&gt; :mostrar_productos, :id =&gt; empresa.id } end end </pre> <p>Esto es sin duda algo mucho mas elegante... espero sus comentarios.</p> <p>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).</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/03/23/eficiencia-del-captcha Eficiencia del captcha 2007-03-23T19:04:50+00:00 2007-11-06T07:42:19+00:00 <p>Hace rato que quería poner en consideración el tema del captcha. (<a href="http://es.wikipedia.org/wiki/CAPTCHA" title="http://es.wikipedia.org/wiki/CAPTCHA" id="link_0">El captcha es esto</a> )</p> <p>Así que vamos al código:</p> <p>Antes que nada cree un controlador para el captcha, se necesita instalar la gema <a href="http://rmagick.rubyforge.org/" title="http://rmagick.rubyforge.org/" id="link_1">RMagick </a> (perdí el enlace al blog de donde me basé para esto):</p> <pre> class CaptchaController &lt; 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 &lt;&lt; Magick::Image.new(medidas.width, medidas.height){ self.background_color = '#fff' } # poner texto al canvas canvas &lt;&lt; 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 &lt;&lt; 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 =&gt; 'captcha.jpg', :type =&gt; 'image/jpeg', :disposition =&gt; 'inline') end end </pre> <p>En la primera línea utilizo el generador de aleatorios que ya postee en este mismo blog en otro momento.</p> <p>Luego de esto en la vista llamo al captcha con una imagen normal, colocándole el <em>src</em>, apuntando al controlador.. algo así como:</p> <pre> &lt;img src="#{url_for(:controller =&gt; 'captcha', :action =&gt; 'generar', :time =&gt; Time.now)}" /&gt; </pre> <p>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.<br /> 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<br /> concuerda con la imagen del número, entonces allí en el directorio<br /> encontraremos archivos como: 0000.jpg, 2345.jpg, etc.</p> <p>) :</p> <pre> class CaptchaController &lt; 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 =&gt; 'captcha.jpg', :type =&gt; 'image/jpeg', :disposition =&gt; 'inline') end end </pre> <p>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 <span style="color: rgb(0, 153, 0);">Rmagick</span>. En contraste en el directorio <span style="color: rgb(0, 153, 0);">public/images/captcha</span> quedaron algo así como 10 MB de imágenes.</p> <p>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:</p> <pre> 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 &lt;&lt; Magick::Image.new(medidas.width, medidas.height){ self.background_color = '#fff' } # poner texto al canvas canvas &lt;&lt; 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 &lt;&lt; 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" &lt;&lt; texto if x &lt; 10 texto = "00" &lt;&lt; texto if x &gt;= 10 and x &lt; 100 texto = "0" &lt;&lt; texto if x &gt;= 100 and x &lt; 1000 generar_imagen(texto) end </pre> <p>Como siempre, saludos y espero sus comentarios...</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/03/22/acomo-poner-codigo-rjs-una-vista- ¿Como poner código rjs en una vista? 2007-03-22T14:31:44+00:00 2007-11-06T07:41:56+00:00 <p>Hola de vuelta.<br /> Resulta que durante el desarrollo necesité crear la siguiente funcionalidad:<br /> "Cuando el usuario haga click en un link, se debe desplegar los detalles de información de ese link. En los detalles debe aparecer un link "cancelar" que esconda nuevamente los detalles."<br /> La idea era realizar algo similar a la creación cuando se utiliza <a href="http://www.ajaxscaffold.com/">AjaxScaffold</a>.</p> <p>La primera parte se realizó utilizando una llamada Ajax que obtenía los detalles del link presionado.<br /> Para la segunda parte "esconder nuevamente el contenido", me molestó mucho tener que escribir nuevamente un archivo rjs, lo que implicaba realizar un nuevo llamado Ajax desde el navegador hasta el servidor para una tarea que no lo necesitaba.</p> <p>Buscando un poco en internet encontré lo siguiente que fue genial!.</p> <pre> &lt;!-- Parametros: nc (una nueva_calificacion) --&gt; Email: &lt;%= nc.email %&gt; &lt;br/&gt; Nombre: &lt;%= nc.nombre %&gt; &lt;br/&gt; Fecha: &lt;%= nc.fecha_creacion %&gt; &lt;br/&gt; Calificacion: &lt;%= nc.valor %&gt; &lt;br/&gt; Comentario: &lt;%= nc.comentario.contenido %&gt;&lt;br/&gt; &lt;br/&gt; &lt;%= link_to_function(<span style="color: rgb(51, 102, 255);">'Cancelar'</span>, update_page <span style="color: rgb(204, 0, 0); font-weight: bold;">do</span> |page| page.visual_effect(<span style="color: rgb(255, 0, 0); font-weight: bold;">:blind_up</span>, <span style="color: rgb(51, 102, 255);">"nc_detalle_#{nc.id}"</span>) <span style="color: rgb(204, 0, 0); font-weight: bold;">end</span>) -%&gt; </pre> <p>Saludos!</p> RoR-Lab http://s3.amazonaws.com/lcp/ror-lab/myfiles/img165x65.JPG http://ror-lab.lacoctelera.net/post/2007/03/22/clase-generar-aleatorios Clase para generar aleatorios 2007-03-22T14:30:34+00:00 2007-11-06T07:41:56+00:00 <p>En mi última entrada en este blog, mostré el método que permitía generar una llave aleatoria.<br /> Las cosas han ido evolucionando y resultó que uno de los formularios del sistema requería el famoso <a href="http://es.wikipedia.org/wiki/CAPTCHA">CAPTCHA</a> que igualmente debía generar 4 caracteres aleatorios.<br /> RoR fomenta fuertemente el principio <a href="http://es.wikipedia.org/wiki/DRY">DRY</a> (<b>Don't Repeat Yourself</b>). Y bueno, en cualquier otro lenguaje es mejor no repetir código.<br /> De manera que escribí una clase que generara aleatorios. Esta clase es como una especie de utilidad, por lo tanto debe ir dentro del directorio <span style="font-style: italic; color: rgb(204, 0, 0); font-weight: bold;">lib</span> ; allí cree un directorio <span style="font-style: italic; color: rgb(204, 0, 0); font-weight: bold;">utilidades</span> y dentro de este la clase GeneradorAleatorios; acá les presento el código para que me den sus opiniones:</p> <p><span style="color: rgb(153, 0, 0); font-weight: bold;">class</span> Utilidades::GeneradorAleatorio</p> <p>CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a<br /> NUMBERS = ("0".."9").to_a</p> <p><span style="color: rgb(0, 102, 0);"> # genera una palabra aleatoria con la cantidad de caracteres especificada</span><br /> <span style="color: rgb(0, 102, 0);"> # utiliza las letras de la 'a' a la 'z' minúsculas y masyúsculas y los números del '0' al '9'</span><br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">def</span><span style="color: rgb(153, 0, 0);"> </span>self.generar_palabra(cantidad = 10)<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">return </span>self.generar(cantidad) { CHARS }<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">end</span></p> <p><span style="color: rgb(0, 102, 0);"> # genera una número aleatorio con la cantidad de dígitos especificada</span><br /> <span style="color: rgb(0, 102, 0);"> # utiliza los números del '0' al '9'</span><br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">def </span>self.generar_numero(cantidad = 10)<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">return </span>self.generar(cantidad) { NUMBERS }<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">end</span></p> <p><span style="color: rgb(0, 102, 0);"> # genera una cadena de caracteres aleatoria</span><br /> <span style="color: rgb(0, 102, 0);"> # debe proveerse un bloque que retorne un arreglo de donde se sacarán los caracteres</span><br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">def </span>self.generar(cantidad = 10)<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">if </span>block_given?<br /> caracteres = yield<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">else</span><br /> caracteres = CHARS<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">end</span><br /> aleatorio = ""<br /> 1.upto(cantidad) { |i| aleatorio &lt;&lt; caracteres[rand(caracteres.size)] }<br /> <span style="color: rgb(153, 0, 0); font-weight: bold;">return </span>aleatorio<br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">end</span><br /> <span style="font-weight: bold; color: rgb(153, 0, 0);">end<br /> </span><br /> Así mas o menos se debería usar:</p> <ul> <li>Generar un String de 4 caracteres de un arreglo específico.</li> </ul> <p>Utilidades::GeneradorAleatorio.generar(4) { ['1','0','a','b','z'] }</p> <ul> <li>Generar un String de 40 caracteres, letras mayúculas, minúsculas y digitos:</li> </ul> <p>Utilidades::GeneradorAleatorio.generar_palabra(40)</p> <ul> <li>Generar un String de 30 caracteres, pero solamente dígitos:</li> </ul> <p>Utilidades::GeneradorAleatorio.generar_numero(30)</p> <p>Saludos! </p>