Last month Brice Figureau released a little Nginx module which tracks the progress of uploads going through Nginx. Like Lighttpd's mod_uploadprogress it's very nice to have the proxy take responsibility for reporting the progress.
The Nginx upload progress module adds track_uploads and report_uploads directives which do what they say on the tin. When the user submits a form to the location supporting track_upload, we also send an extra X-Progress-ID parameter with a unique id for that upload. Requests made to the location supporting report_uploads (typically /uploads) will return JSON with the current progress for an upload. The X-Progress-ID header is added to the ajax requests to identify which upload we're wanting to know the status of.
Having Nginx track the progress gets around an issue with using the merb upload progress method. Nginx (un)fortunately does not pass along the upload to its destination until it has fully completed uploading. So when you upload a file, your Merb application won't know about it until it has been upload to Nginx and passed along. The only gripe with this whole process is the possibility of a small delay as Merb handles the request once the upload is complete. If you're using Rails however this can be an advantage, as it won't be tied up by the upload as it happens in realtime.
To get this all going you'll first want to download the Nginx upload progress module and recompile Nginx with the following configure option.
--add-module=path/to/nginx_uploadprogress_moduleLocate and open your nginx configuration file and add in the upload_progress, track_uploads and report_uploads directives.
If you're going to upload files of any considerable size you'll also want to the max_body_side to something reasonable.
http { upload_progress proxied 1m; server { listen 80; server_name _ *; root /usr/local/www; location / { proxy_pass http://127.0.0.1:4000; track_uploads proxied 30s; client_max_body_size 500m; } location ^~ /progress { report_uploads proxied; } } }Nginx is now ready to track uploads! Generate a Merb project and add a new upload controller.
class Uploader < Application def index render end def upload FileUtils.mv params[:file][:tempfile].path, \ MERB_ROOT+"/files/#{params[:file][:filename]}" end endThen add the upload view.
<div id="pandaloader"> <div id="uploader"> <form id="upload" enctype="multipart/form-data" \ action="/uploader/upload" method="post"> <input name="file" type="file"> <input type="submit" value="Upload"> </form> </div> <div id="uploading"> <div id="progress" class="bar"> <div id="progressbar"> </div> </div> </div> </div>In the header of your application's layout you'll also need to include the javascript and css.
<%= css_include_tag 'upload' %> <%= js_include_tag 'jquery' %> <%= js_include_tag 'jquery.nginxUploadProgress.js' %> <script type="text/javascript" charset="utf-8"> $(function() { $('#upload').nginxUploadProgress(); }); </script>Aside from jQuery, you'll notice the jquery.nginxUploadProgress.js plugin which has been included. This is a little jQuery library which will query Nginx for the upload's progress and update the length of the progress bar accordingly. Grab the file here: jquery.nginxUploadProgress.js
Here is some styling for the progress bar.
.bar { width: 300px; } #progress { background: #eee; border: 1px solid #222; } #progressbar { width: 0px; height: 24px; background: #333; }Put all these together and you'll have a great little progress bar for uploads!
Ahora que han pasado unos meses desde el lanzamiento de Java 6 y que en casi en cualquier provincia puedes expedir un dni electrónico ya podemos empezar a pensar como hacer uso de ambos.
Dentro de nuestro dnie se encuentran varios certificados digitales para firma electrónica o autenticación, para acceder a ellos necesitamos es un lector de tarjetas que acepte nuestro documento.
Una de las nuevas incorporaciones en java 6, y de las menos publicitadas, es el soporte nativo para acceder al almacen de claves de windows. Hasta ahora teníamos que usar complejas DLLs de windows para acceder a este almacen, pero con esta nueva release es tan fácil como esto:
KeyStore keyStore = KeyStore.getInstance("Windows-MY"); keyStore.load(null, null);Como no todos en este mundo usamos ese sistema operativo, dentro de nuestro dnie también podremos encontrar los certificados dentro de una librería PKCS#11 llamada opensc-pkcs11.so que podremos usar en cualquier otro sistema. El proceso de acceder a una librería PKCS#11 es un poco más tedioso pero hay muy buenos artículos en la red que lo explican . Una aproximación rápida podría ser que tenemos que añadir esta librería como un proveedor de certificados a nuestro almacen de claves PKCS#11 y luego acceder a este para recuperar los certificados. Para añadir la librería a nuestro almacén necesitaríamos algo como esto:
String pkcs11config = "name = DNIE\nlibrary = opensc-pkcs11.so "; InputStream confStream = new ByteArrayInputStream(pkcs11config.getBytes()); Class sunPkcs11Class = Class.forName("sun.security.pkcs11.SunPKCS11"); Constructor pkcs11Constr = sunPkcs11Class.getConstructor(InputStream.class); Provider pkcs11Provider = (Provider) pkcs11Constr.newInstance( confStream ); Security.addProvider(pkcs11Provider);para acceder a este almacén:
KeyStore keyStore = KeyStore.getInstance("PKCS11"); keyStore.load(null, password);Una vez cargado nuestro almacén de claves correspondiente podríamos acceder a todos los certificados que contiene a través de sus alias:
keyStore.load(null, null);When using the nginx_uploadprogress_module you need to set the X-Progress-ID header in your progress polls. Safari sanitises header names, so X-Progress-ID ends up becoming X-Progress-Id in your request. Unfortunately Firefox does just the oppose renaming X-Progress-Id to X-Progress-ID (but only for this particular header). The current release of nginx_uploadprogress_module is case sensitive, so I've patched it to be case insensitive.
Nginx Upload Progress Module Safari patch

Frozen gems in Merb == gems installed inside the application.
That's clever because now you can use the standard tools for installing and managing the installed gems. For example, you get dependency checking and so on for free. Add -i gems to the command line options to specify the install location.
gem install merb-core -i gemsThat's the simplest case (run it from the root of you merb app). It fetches the merb-core gem from the gem servers and installs it inside a gems directory.
However, in many cases that's not what you want. Really you want to freeze the version you're currently developing on. Maybe one you've installed from the merb-core git repository. That's really easy because you have the gem package files on your system already. For me they're in /Library/Ruby/Gems/1.8/cache. Get your location by running gem environment gemdir and look for a cache folder. Install the package with
gem install /Library/Ruby/Gems/1.8/cache/merb-core-0.9.1.gem -i gemsYou should add /gems/doc to your .gitignore since you definitely don't want ri and rdoc files in your repository. Alternatively add --no-ri --no-rdoc to the gem install command.
<!-- I assume you can also add <code>/gems/cache</code> to the .gitignore file. -->
The final challenge is actaully getting merb started. Right now I'm relying on a pastie that google found for me (I'm sorry I have no idea who the author is). Make that executable and use it to run merb with the usual merb command line options. That's surely a bit of a hack though and I'm hoping that someone will point out a better way :)
First a simple solution using throw_content and catch_content.
This is what sublayout.html.erb looks like
<% throw_content :sublayout do %> <div class="header"> ... </div> <%= catch_content :for_layout %> <div class="footer"> ... </div> <% end %> <%= render catch_content(:sublayout), :layout => "application" %>Your main layout contains <%= catch_content :for_layout %> as usual.
You might find it handy to use catch and throw content in a few more places. For example, in your sublayout you could <%= catch_content :sublayout_title %> and throw it in the view with
<% throw_content :sublayout_title %> Hippos <% end %>As a little bonus I wrote a helper so that you can use the same syntax as you're used to from the rails plugin. Just add this snippet into Merb::GlobalHelpers
def inside_layout(layout, &block) content = render(capture(&block), :layout => layout) concat(content, block.binding) endThen your sublayout looks like
<% inside_layout :sublayout do %> <div class="header"> ... </div> <%= catch_content :for_layout %> <div class="footer"> ... </div> <% end %>Here's another idea which I think would be even better: What if you could say layout ['application', 'sublayout'] in your controller and have each layout rendered inside it's parent all up the chain? Then sublayout.html.erb wouldn't be constrained to always render inside application.html.erb and you could use it in a much more flexible way. However Damien things it's a mad idea. What do you think?
La posibilidad del acceso a la propiedad por cada uno y el desarrollo general del comercio, son pues las bases econ??micas de cualquier ciudadan??a que no consista en una mera representaci??n. Es a esta sencilla verdad a la que llamamos neovenecianismo.
Exploradores Electr??nicos es una red de mercaderes y emprendedores libres dedicada al prop??sito de la construcci??n y experimentaci??n de un espacio propio de ciudadan??a libre, constituida sin coherci??n grupal o estatal alguna y consagrada al desarrollo de un espacio transnacional y desterritorializado en el que profundizar las libertades y derechos que hacen posible una vida plena en comunidades solapadas, pluri??rquicas y no cohercitivas.
M??s en Constituci??n de los Exploradores Electr??nicos.
Las plantillas de código (code templates en inglés) son pequeños snippets de código fuente que se expanden automáticamente permitiéndonos modificar determinadas partes, mientras que otras partes se mantienen fijas. Aunque son una funcionalidad disponible en todo editor que se precie, no todo el mundo parece darse cuenta de su potencial: las plantillas de código son a los editores / IDEs lo que el DRY es a la programación.
Las plantillas se invocan normalmente escribiendo una pequeña palabra en el editor y pulsando una tecla o una combinación de teclas para “dispararlas”. Para quien no conozca el mecanismo, las dos siguientes imágenes sirven como ejemplo: al escribir en el editor del NetBeans la palabra def y pulsar a continuación la tecla tab (primera imagen), obtenemos la expansión de una definición de un método en Ruby (segunda imagen).
El cursor queda situado automáticamente en method_name, permitiéndonos escribir el nombre que queramos, y una vez pulsamos enter o bien tab de nuevo, el cursor pasa al cuerpo del método, para que podamos seguir codificando.
NetBeans trae una serie de plantillas definidas por defecto, pero a través del menú Tools > Options > Editor > Code templates podemos acceder a un panel en el que, además de poder ver todas esas plantillas predefinidas, podemos crear las nuestras propias. Hay bastantes opciones a la hora de controlar cómo se expande la plantilla; para una descripción más exhaustiva de todas ellas, aquí está la página del wiki de NetBeans dedicada a las RubyCodeTemplates.
Aquí sólo vamos a mostrar la definición de la plantilla def tal y como viene “de serie” con el NetBeans:
La sintaxis es sencilla:
Las posibilidades son muchas (no tantas como en el TextMate, quizás, pero muchas al fin y al cabo :P). Como ejemplo, sirvan un par de plantillas que utilizamos muy a menudo en nuestros editores:
Plantilla b <% ${0 default="block"} do %> ${selection}${cursor} <% end %>
Con esta plantilla definimos bloques en Erb, algo que hacemos muy a menudo ya que normalmente usamos helpers de este tipo para capturar las distintas partes de nuestro layout.
Una cosa que hemos aprendido con la experiencia: algunas plantillas, se utilizan tanto para rodear código ya existente como para generar código desde cero. En esos casos el truco de poner ${selection}${cursor} permite utilizar la plantilla para ambos fines. ¡Y otra cosa importante! Las plantillas que incluyen el parámetro ${selection} se pueden invocar de dos maneras: utilizando una tecla (p.e. tab), en cuyo caso contarán con una selección vacía, o utilizando la combinación Alt+Enter. En funcionamiento:
Plantilla t <${0 default="div"} id="${1 default=""}">${selection}${cursor}</${0}>
Esta plantilla la utilizamos para generar tags HTML con un id. En este caso el parámetro 0 se sustituye con el nombre del tag, tanto en la apertura como en el cierre. El parámetro 1 se sustituye por el id del elemento. Y de nuevo utilizamos el truquillo de la selección para poder rodear HTML si lo deseamos.
Son sólo dos ejemplos, pero en nuestro trabajo diario utilizamos un buen puñado de plantillas que nos permiten acelerar las tareas de codificación y evitar errores al teclear. Pensad en el código que repetís constantemente y definid vuestras propias plantillas para facilitaros la tarea; una vez lo hagáis no podréis vivir sin ellas.
RSpec stories are a way of doing integration and acceptance testing using plaintext executable tests. You can use them in Merb as well as Rails. Here's how.
Setting upInstall edge Merb; the latest gem (0.9.2) will not work. You need merb-core, merb-more, and merb-plugins.
Merb-plugins gives you the merb_stories gem, so you don't need to install that separately.
Add this line to your app's config/environments/test.rb:
dependencies "merb_stories", "webrat"(Note that merb_stories' README file is wrong about this, for now - and the generator will create a dependency on merb-rspec, which no longer exists. My fork fixes this.)
Now generate your story:
merb-gen story mystoryNow run your story:
rake story\[mystory\]Yes, you must include the square brackets, and you have to escape them.
Writing storiesNow fill out your story. There are some differences to Rails' versions. The best places to look for help are in the Merb code itself:
To start you off, here are the steps for a simple integration test:
steps_for(:homepage) do When("I visit the root") do @mycontroller = get("/") end Then("I should see the home page") do @mycontroller.should respond_successfully @mycontroller.body.should contain("Hello") end endAs you write your tests, don't trust Merb absolutely. Some things are wrong, don't work, or aren't meant to work [yet]. As part of debugging, look at the Merb source, and fork it and fix it if needed.
Adding WebratWebrat lets you write integration tests that are even closer to natural language. You can say things like:
visits '/auth/login' fills_in 'username',:with=>"bob" fills_in 'password',:with=>"hunter2" clicks_button "login" response.should be_successful ...I've forked Webrat to add Merb support.
To get started, clone it, build the gem and install it.
You'll also need to make sure you're using the memory session store (for testing), or your sessions won't be preserved:
Merb::Config.use do |c| c[:session_store] = 'memory' end Continuous integrationAutotest will run your specs continuously, but won't run your stories. You can fix this, but stories run very slowly, since they use the full stack. At the very least, though, you want to run your stories after you deploy, in case you have "works on dev machine, dies on production" problems.
Add this to deploy.rb:
namespace :deploy do task :after_deploy do run_remote_tests end desc "Run tests on remote server" task :run_remote_tests do run "cd #{deploy_to}/current && rake spec" run "cd #{deploy_to}/current && rake story[all] MERB_ENV=test" end endThis will fail if you're using Vendor Everything like us - it won't be able to find merb-core. To fix it, replace stories/stories/all.rb:
env = ENV['MERB_ENV'] || 'test' require 'rubygems' Gem.clear_paths Gem.path.unshift(File.join(File.dirname(__FILE__), "..","..","gems")) require 'merb-core' Merb.load_dependencies(:environment => env) require 'spec' Merb.start_environment(:testing => true, :adapter => 'runner', :environment => env) dir = File.dirname(__FILE__) Dir[File.expand_path("#{dir}/**/*.rb")].uniq.each do |file| require file endYou'll have the same problem with specs. Edit spec/spec_helper.rb:
Gem.clear_paths Gem.path.unshift(File.join(File.dirname(__FILE__), "..","gems"))Now you have natural-language integration tests that run automatically on deployment. Sweet.
Tal como se ha publicado en el blog de Cenatic, ya se puede descargar una versión de pruebas de la herramienta GONG de Gestión de ONGs.
GONG es un software de gestión de proyectos enfocado a ONGs, financiado por Cenatic y llevado a cabo por una UTE formada por BGS, Entel y Fundación Iepala (dentro de la cual se estableció una participación de las empresas Semilla de software libre, Dabne y Xsto ) y un consorcio de 7 ONGs .
Dabne ha participado en el desarrollo junto a IEPALA y Entel. Nuestro trabajo de desarrollo se ha centrado en la integración entre Ruby on Rails y Alfresco, las dos plataformas sobre las que se ha desarrollado la herramienta. Además, Dabne ha participado en el desarrollo del plan de la comunidad y está en participando, en estos momentos, en la implantación de la herramienta en las ONGs del Consorcio definido en la fase de inicial del proyecto, así como en la formación a los responsables de las ONGs en el uso de la misma.
GONG despertó gran interés en la Conferencia Internacional de Software Libre, celebrada en Málaga en octubre, en la que se hizo una presentación por parte de Cenatic, como refleja este artículo del diario Púlblico.
Siempre que hablamos de metaprogramación parece que los ejemplos son un poco “de laboratorio”, más académicos que otra cosa, así que aquí tenemos un truquito rápido sacado de código real, que no es el invento de la pólvora ni nada que no pudiéramos hacer de una forma más convencional (y más larga y proclive a errores), pero la verdad es que la cosa queda bonita.
Pongamos que tenemos un modelo Article, que puede tener varios estados, como borrador, publicado, oculto, etc. Guardar ese estado como una cadena es una guarrería, así que un enfoque típico sería guardarlo como un entero, y tener una serie de constantes que hagan algo más legible el código:
class Article < ActiveRecord::Base PENDING = 0 PUBLISHED = 1 UNPUBLISHED = 2 ... endY luego hacer cosas como:
Article.find(:all, :conditions => [“status = ?”, Article::PENDING]) ... do_something if article.status == Article::PUBLISHED ... article.status = Article::UNPUBLISHEDSin embargo, podemos hacerlo mucho mejor. Podéis llamarme pijo por preferir, con mucho, hacer cosas así:
Article.find_pending ... do_something if article.published? ... article.unpublished!Programando uno puede ser todo lo pijo que quiera porque puede fabricarse sus propios caprichos, y escribir todos esos métodos:
def pending? self.status == PENDING end def pending! self.status = PENDING end def self.find_pending find(:all, :conditions => ["status = ?", PENDING]) end ...Y así para cada estado, y otra vez más si en una semana se te ocurre añadir otro estado, y después comprobar que no has cometido ningún error en tooooodo ese código, ni te has saltado involuntariamente tus propias convenciones.
Lo malo es que yo además de pijo soy vago. Efectivamente, se puede hacer mejor, más DRY, y con menos posibilidad de errores. Pongo el código y después lo explico:
class Article < ActiveRecord::Base STATUSES = { :pending => 0, :published => 1, :unpublished => 2 } STATUSES.each do |status, value| define_method :"#{status}?" do self.status == value end define_method :"#{status}!" do self.status = value end end class << self STATUSES.each do |status, value| define_method :"find_#{status}" do find(:all, :conditions => ["status = ?", value]) end end end endBien, en primer lugar hemos modificado la definición de los estados para hacerlo con un hash. ¿Para qué? Pues para poder iterar por él. En el primer caso, iteramos sobre la lista de estados y para cada uno definimos dos métodos, el acabado en ”?” y el acabado en ”!”, ambos de implementación trivial.
El truco está en define_method, que hace lo mismo que def sólo que es un método en sí mismo y por tanto nos da más flexibilidad, como por ejemplo poder usarlo de forma iterativa, o pasarle un parámetro (que será el nombre del método definido) de forma dinámica.
Después volvemos a repetir el truco pero a nivel de la clase, para definir el método de clase que queríamos tener. Y de esta forma, tendremos estos tres métodos para cada uno de los estados posibles. Y lo mejor es que, cuando la semana que viene alguien se de cuenta de que hace falta otro estado más, bastará añadirlo a la lista, y tendremos nuestros tres métodos, gratis.
Por cierto, esta es una implementación bastante sencilla del tema de los estados, aunque para el caso que me ocupaba más que suficiente. Si necesitáis algo un poco más complejo, os recomiendo que le echéis un ojo al plugin acts_as_state_machine, que hace esto que he explicado (aunque lo hace de otra manera), y muchas más features, como por ejemplo (para mí lo más chulo), callbacks que se ejecutan cuando el objeto pasa de un estado a otro.
Zed's rant triggered some patently false anti-Ruby memes that have now been bouncing around the programming blogsphere echo chamber for a few weeks. Disturbingly so. It's time to put a bullet to the head of the idea that Ruby is experiencing a widespread backlash, that it was just a fad, or that it is inferior to competing technologies such as Groovy. As far as I can tell, the originators of these ideas are people that betray agendas against the success of Ruby and/or Ruby on Rails. Specifically, I'm calling one of them out by name:
Daniel Spiewak (for being a liar)
His post, The End of the Ruby Fad? really set me off and was the final straw that made me write this post. As a community, I don't think we can just let the FUD and lies perpetuate unchecked. Daniel's post in question is full of wrong-headed opinions, but it also has lies in it that I believe are specific enough to be called out as willful deception:
Daniel: "Ruby posts to link sites like DZone or Reddit get voted down before they have a chance to see the light of day."Simply browse the ruby links on programming.reddit.com or the list of over 2000 ruby-related links on DZone to disprove that lie. My contention, which I think is fairly obvious to people on this side of the Ruby/Anti-Ruby divide, is that the people echoing the Ruby backlash theme are all folks with entrenched anti-Ruby stances and agendas. There are no neutral observers chiming in on the matter.
It's the same old FUD, repackaged:
Daniel: "...while Ruby may not be suitable for an enterprise level, high-traffic web application,"Peter Cooper of RubyInside calls him out on that one better than I could hope to do so:
Peter: "You say you don’t like the hype or the backlash, but then you come out with this sort of vague non-statement to keep the fire burning. Any language “may” or may not be suitable for any task, but you seem to be implying in the context here that Ruby is “not” suitable for developing enterprise level, high-traffic Web applications without actually going the whole hog and just saying it. In any case, this, of course, is not true. At least, it’s no more true for Ruby than it is for Python, Perl, PHP, or Scala, say."What about the most general of facts, about the nature of Ruby itself as a programming language?
Daniel: "(Ruby is) hardly a general-purpose language, so it could never replace Java and company."Ruby is by definition, a general-purpose language. (Wikipedia makes a contrast between domain-specific and general-purpose languages.)
You have to question the actual knowledge of someone making such blatantly wrong assertions. In other words, does Daniel know what the hell he's talking about? He answers that question himself in the comment trail:
Daniel: "It has been pointed out that my "Rails" example is quite foolish and naive. This is absolutely true."Oh the irony! How folksy and cute to write blatant crap in your blog, enjoy the publicity and then admit that you knew it was crap to begin with. To that I say "UNACCEPTABLE!"
Even his rhetoric is faulty, like this plainly false categorization of developers:
Daniel: "Developers these days fall into two camps: those who have heard the hype and rejected what it stands for, and developers who are totally carried away by the emotion of the fad."Maybe not technically a lie, more of an opinion, but certainly bad rhetoric. There is a huge third camp, that dwarfs the extremists on both sides, full of intelligent, pragmatic individuals that are choosing to use the tools available to us without prejudice. That includes Java, Ruby, Python, Scala, etc. There is no silver bullet, and all that jazz...
Oppression
Daniel's post led me to question, what is the motive for piling on to the heap of negativity started by Zed and perpetuated by Rick Hightower, Graeme Rocher and others? Why pick on the Ruby community? Is it simply a reaction to the hype cycle?
Daniel: "Perhaps now that the bubble has burst, we’ll finally get to see the popularity of Ruby in its proper place."Aha! Above all, that sentiment is the common thread amongst the haters of the progressive Ruby community. Oppression! They want to keep Ruby (and by extension Rubyists) in our proper place! What is that proper place I wonder -- perhaps it is out of the mainstream, out of the limelight, out of the enterprise, out of the places where we threaten the status quo!
For some time, Hudson has become my favorite continuous integration server. It's easy to configure and provides a handful of really interesting plug-ins. The only thing that I missed was the possibility of use Rake as a project build tool and thus I'll be able to take my java, ruby or rails projects into the same CI server.
Well, past weekend I was too much spare time so I decided to develop my first Hudson plugin and this morning I've released the first version of the Hudson Rake plugin.
Once you have installed Hudson you just need to donwload the plug-in and upload it from the Manage Hudson section:

When the plugin is avalable it detects your ruby instances installed from your PATH but it allows you to add other ruby or jruby paths:

Finally you just need to select the Invoke Rake option into the project configuration and select the tasks that you want to Hudson executes:

That's it, you are ready to go with Rake, Hudson and the Continuous Integration Game.
Según su propia página web, cacti es una solución completa para generar gráficas aprovechando toda la potencia que nos brinda RRDTool. Después de tres años de espera, en octubre de 2007 se anunció la versión 0.8.7 de cacti. En su día escribí un documento donde contaba los detalles de cómo instalar cacti (0.8.6) en Solaris 8. En este post contaré cómo instalar la nueva versión de cacti (0.8.7) en Solaris 10.

Antes de poder instalar cacti, necesitaremos instalar Apache + Mysql + PHP. En este post puedes leer cómo hacerlo.
Instalación de RRDToolLa manera más sencilla de instalar RRDTool es instalar el paquete de sunfreeware. Este paquete depende de los siguientes paquetes:
Pero si antes hemos instalado Apache + MySQL + PHP con los paquetes de sunfreeware, el único paquete que nos quedará por instalar es libart_lpgl. Por lo tanto, para instalar RRDTool, debemos instalar los siguientes paquetes de sunfreeware:
Necesitaremos también añadir /usr/local/rrdtool-1.2.19/bin a la variable PATH.
$ rrdtool RRDtool 1.2.19 Copyright 1997-2007 by Tobias Oetiker <tobi@oetiker.ch> Compiled Mar 24 2007 00:31:10 Instalación de Net-SNMPLa instalación es muy sencilla, simplemente instalamos el siguiente paquete de sunfreeware:
y añadimos /usr/local/bin al PATH.
Instalación de cactiDescargamos cacti, volcamos su contenido en el DocumentRoot de nuestro Apache y cambiamos el propietario y el grupo del nuevo directorio:
# cd /usr/local/apache2/htdocs # gzcat cacti-0.8.7a.tar.gz | tar xf - # rm cacti-0.8.7a.tar.gz # ln -s cacti-0.8.7a cacti # chown -R cactiuser:cactigroup cacti-0.8.7aApuntamos nuestro DocumentRoot al directorio de cacti. Lo más sencillo es que el propietario y el grupo de cacti sean los mismos que el usuario y el grupo con el que se ejecuta Apache. Estos serían los cambios que habría que hacer en el httpd.conf de Apache:
DocumentRoot "/usr/local/apache2/htdocs/cacti" User cactiuser Group cactigroupRearrancamos Apache para que los cambios sean tenidos en cuenta:
# /etc/init.d/apache stop Parando Apache # /etc/init.d/apache start Arrancando ApacheCreamos en MySQL una base de datos para cacti, y creamos el modelo de datos importando el script cacti.sql:
$ cd /usr/local/apache2/htdocs/cacti $ mysqladmin --user=root -p create cacti $ mysql --user=root -p cacti < cacti.sqlCreamos un usuario para cacti en la base de datos y le asignamos una contraseña:
$ mysql --user=root -p cacti mysql> GRANT ALL ON cacti.* TO cacti@localhost IDENTIFIED BY 'password'; mysql> flush privileges;Editamos el fichero de configuración de cacti, include/config.php:
$database_type = "mysql"; $database_default = "cacti"; $database_hostname = "localhost"; $database_username = "cacti"; $database_password = "password";En nuestro navegador, abrimos la URL http://localhost/ y nos aparecerá la pantalla de bienvenida a la instalación de cacti:

En la siguiente pantalla seleccionamos:
type of installation > New InstallDespués, en la siguiente pantalla, nos tenemos que asegurar de que todas las utilidades tienen bien configurado su path.
Lo siguiente que nos aparecerá será la pantalla de login:

Aquí nos conectaremos como admin/admin y se nos pedirá una nueva contraseña. Y ya está, después de seleccionar una nueva contraseña nos aparecerá la consola de administración de nuestra instalación de cacti:



Me parecen muy interesantes las iteraciones que están realizando en 20 Minutos en su menú de navegación principal. Si no me equivoco, han ido ajustando etiquetas y puliendo píxeles poco a poco, y supongo que seguirán haciéndolo. Por un lado formalmente, están consiguiendo un menú muy ligero y con mucho contenido, y en cuanto a las secciones que referencian, son toda una declaración de intenciones de dónde está el periódico.
Han abandonado totalmente las secciones temáticas habituales de un periódico (¡ya era hora que alguno lo hiciese!).
En el primer submenú desplegado (el que aparece visible cuando entras en la home) destacan:
El segundo elemento de navegación principal es “Tu Ciudad”, que despliega todas las ciudades en las que tienen presencia - desde el principio han apustado por lo local, creando ediciones específicas en un montón de ciudades, y de un tiempo a esta parte estan potenciando esto en la web, creando ediciones locales que en papel no podrían exisitir:

A continuación se centran en secciones muy rentables (en cuanto a audiencia, y supongo que por tanto en cuando a dinero):
Esta mañana he estado pegándome con el plugin restful_authentication para que activase correctamente los usuarios. Por "suerte" Jaime estaba en las mismas y después de hablar un ratillo por jabber hemos conseguido que funcionase correctamente. Había que hacer un par de cosas que no están bien explicadas en la documentación del plugin y que hemos encontrado en los comentarios de la página del plugin en el directorio de plugins de agilewebdevelopment.com.
Dejo aquí apuntados los pasos para futura referencia.
Primero instalar el plugin:
$ script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/Crear los usuarios y las sesiones con activación de usuarios con el generador que trae el plugin:
$ script/generate authenticated user session —include-activationAñadir las rutas necesarias en config/routes.rb, según el README del plugin:
map.resources :users map.resource :session, :controller => 'session' map.signup '/signup', :controller => 'users', :action => 'new' map.login '/login', :controller => 'session', :action => 'new' map.logout '/logout', :controller => 'session', :action => 'destroy'Y una que no viene en la doc pero que es necesaria para que funcionen los links de activación enviados por email.
map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate'Configurar ActionMailer para que envíe correo. Poner las urls correctas para nuestra aplicación en app/models/user_mailer.rb. En lugar de ponerlas a pelo, se puede definir HOST en environment.rb con:
HOST = "localhost:3000"y luego en app/models/user_mailer.rb poner:
@body[:url] = "#{$HOST}/activate/#{user.activation_code}"Por último, no queremos que el usuario quede logueado al registrarse, si no ¿qué sentido tiene la activación? Para esto hay que comentar la línea que dice self.current_user = @user después de @user.save! en el método create de users_controller.rb:
def create cookies.delete :auth_token @user = User.new(params[:user]) @user.save! # No logueamos al usuario hasta que se haya validado #self.current_user = @user 'new' endEste problema me ha dado la lata un buen rato. Me animo con este post porque no he encontrado prácticamente ninguna información sobre cómo hacer esto. El problema que queremos solucionar es sencillo de explicar:
Tenemos una entidad llamada FooData. Queremos recuperar n objetos fooData aleatorios.
Suponiendo que la tabla correspondiente a FooData es foo, en mysql podemos resolver nuestro problema con una query tal que así:
select * from foo order by rand() limit n;En Oracle 9 la query es un poco más compleja, sobre todo por la parte de limitar el número de registros a devolver, que se resuelve con una subquery:
select * from ( select * from rolleruser order by dbms_random.value) where rownum < nUna vez que sabemos resolver nuestro problema en SQL y que ya hemos visto que no hay una solución estándar, vamos a tratar de implementarlo mediante HQL.
La parte de obtener únicamente n resultados es trivial, ya que disponemos del método org.hibernate.Query.setMaxResults(int arg0). El esqueleto de nuestro código sería parecido a:
Session session = ((HibernatePersistenceStrategy)this.strategy).getSession(); Dialect currentDialect = ((SessionFactoryImplementor) session.getSessionFactory()).getDialect(); String queryString = null; if (currentDialect instanceof Oracle9Dialect) { // Oracle 9 specific queryString = "CUSTOM_QUERY_1"; } else { // MYSQL specific queryString = "CUSTOM_QUERY_2"; } Query query = session.createQuery(queryString); query.setMaxResults(amount);Donde será necesario sustituir CUSTOM_QUERY_1 y CUSTOM_QUERY_2 por los valores adecuados en cada una de las ramas del if.
HQL para obtener n registros aleatorios con MySQLPara hacer esta consulta con HQL "sobre" mysql, la traducción es casi directa. Eliminamos el limit, pues ya limitamos el número de resultados con setMaxResults() y cambiamos el nombre de la tabla por el nombre de la entidad, de modo que en el ejemplo anterior donde teníamos CUSTOM_QUERY_1, ahora tendremos:.
from FooEntry foo order by rand(); HQL para obtener n registros aleatorios con Oracle 9Esta traducción es casi tan trivial como la anterior, pero con matices. Utilizando la misma lógica que en el apartado anterior llegamos a una query más o menos así:
from FooEntry foo order by dbms_random.value;Esta query no funciona, pues nuestro amigo Hibernate trata de buscar el atributo value de la entidad dbms_random. Y no encuentra el mapping, porque no lo hay. Solo tenemos que buscar la manera de que la BD reciba la query adecuada. Tras un buen rato de ensayo-error, llegamos a la siguiente solución:
from FooEntry foo order by dbms_random.random() Ahora nuestro driver si que es capaz de "saber" que dbms_random.random() no es cosa suya y que lo único que tiene que hacer es dejar pasar ese trocito de query para que Oracle juegue con el.
Sustituyendo CUSTOM_QUERY_2 por esta última query, ya tendremos el puzzle completo.
Existen dos tipos de dispositivos de almacenamiento. Los que han fallado y los que están a punto de fallar.
– Dicho popular entre los administradores de sistemas.
(Visto en el blog de Jonathan Schwartz)
# Enlace permanente
Lo prometido es deuda: un nuevo post. Hoy: Haml. ¿Comparte realmente las características de un buen haiku?, ¿es breve, conciso, evocador, bello, etc?.
Nota: Este post no va a explicar qué hace o cómo se usa Haml. Eso podéis descubrirlo ampliamente en su página web, que debéis visitar para poder seguir leyendo.
Admito que mi experiencia con Haml no es muy amplia. Hasta hace poco sólo había usado ERB y Markaby. Por lo que había leido del tema, Haml se me antojaba similar a Markaby, así que no le di mucha importancia hasta el día en que me tope de bruces con StaticMatic (del que hablaré en otro post… espero). De todos modos, y a pesar de mi corto rodaje me he dado cuenta de que Haml es la típica historia que, o bien odias, o bien amas con locura. Así que tenemos nueva batalla en el mundo Rails: hamelistas contra erebeistas (si nadie ha registrado esas palabras yo lo haré: quiero un euro cada vez que alguién las utilice, jaja..). En Trabe Soluciones, ante estos debates, solemos optar por la tercera vía (la llamada “vía gris”) que se basa en usar las cosas sólo cuando lo consideramos oportuno, sin que medien pasiones u odios.
Lo que nos gusta de HamlEn resumen. Haml está ahí. Está bien pensado. Es útil. No es perfecto. Pero tendréis que probarlo para saber si se adapta a vuestras necesidades. Haml es, en efecto, como un haiku: algo breve cuya belleza es, a veces, discutible:
Mujer agachada
que orina y hace fundir
la nieve
Este post va dedicado a todos aquellos que, como yo, no suelen leerse las "guias de instalación". Si además pretenden instalar una imagen de Windows 2000 sobre VirtualBox en un ordenador tirando a rápido, es posible que tengan un problema.
Al principio parece que todo va bien, pero llega un momento en que el proceso de instalación hace que el PC se reinicie. Al arrancar de nuevo, la instalación vuelve a comenzar. Nos quedamos atrapados en un bucle infinito del que no es posible salir.
Después de perder algún tiempo imaginando qué podía estar pasando, he llegado al origen del problema (manual de VirtualBox):
11.2.2 Windows 2000 installation failuresSiguiendo las instrucciones del manual, todo parece funcionar correctamente. A veces puede ser útil leer un manual...
No ha habido mucha actividad, en términos de grandes cambios o nuevas funcionalidades en el core de Rails. Así que lo mejor es dirigirse a los logs
de los commits en busca de cambios menores o correcciones. Ha habido algunos avances en la refactorización y sorporte de threads en ActionPack, así como alguna actividad en ActiveModel, pero nada realmente concreto.
script/server ahora comprueba si Thin está instalado y lo utiliza. Esto es bastante cómodo si usamos Thin como nuestro servidor en producción y queremos usarlo también en desarrollo. Hay que añadir config.gem ‘thin’ al fichero environment.rb.
Este parche ha sido contribuido por uno de los chicos de fluxin, y estos son los cambios
Personalización de String#humanize mediante reglas de inflexiónLa extensión String#humanize del core se utiliza para convertir cadenas con guiones bajos, por ejemplo nombres de tablas, en texto legible. Por ejemplo:
"Actor salary" "anime_id".humanize => "Anime"Pero a veces esto no funciona tan bien, cuando tenemos tablas heredadas de otra aplicación o simplemente con nombres inhumanos como “act_sal_money” (que sería humanizada como “Act Sal Money”)
Ahora podemos especificar nuestras propias reglas de inflexión (igual que puede hacerse para personalizar las reglas de inflexión para plurales, singulares, etc.)
Inflector.inflections do |inflect| inflect.human /_cnt$/, '\1_count' inflect.human 'act_sal_money', 'Actor Salary' endObsérvese que podemos usar expresiones regulares para convertir por ejemplo columnas como “click_cnt” en “Click Count”
Demos las gracias a Dan Manges y Pascal Ehlert por este cambio.
Condiciones sobre múltiples tablas utilizando hashesPratik ha entregado un pequeño (pero útil) cambio en ActiveRecord que permite especificar las condiciones de una tabla sobre la que se efectúa un JOIN en su propio hash. Por ejemplo:
Anime.all( :joins => :character, :conditions => { :active => true, :characters => { :gender => 'female' } } )La sentencia anterior encontrará todos los animes “activos” con personajes femeninos. Cambios