Tuesday, September 27, 2011

Rails: The power of 'inverse_of'

There is a 'less known' feature of ActiveRecord associations that is very handy: the ability of marking an association as the mirror of another :inverse_of

The best is to rescue an example from the oficial documentation

class Dungeon < ActiveRecord::Base
  has_many :traps, :inverse_of => :dungeon
  has_one :evil_wizard, :inverse_of => :dungeon
end

class Trap < ActiveRecord::Base
  belongs_to :dungeon, :inverse_of => :traps
end

class EvilWizard < ActiveRecord::Base
  belongs_to :dungeon, :inverse_of => :evil_wizard
end
 
dungeon = Dungeon.first
wizard = dungeon.evil_wizard 



In a normal configuration, doing wizard.dungeon will hit the database and instantiate a new 'dungeon' (2 instances of the same object). 
 
With :inverse_of , Active record is aware of the relation and will use the same instance in memory. This has 2 benefits:
- we save a DB call
- as both 'dungeons' are the same instance, modifications on dungeon are seen from wizard (an the otehr way around)
 
Without :inverse_of , a modification on dungeon, is not seen by wizard.dungeon what can lead to subtle errors.
 
+1 for :inverse_of 
 
 

Saturday, September 10, 2011

Capistrano, Database.yml and how to deploy without exposing your database passwords

The other day we wanted to secure our database, changing the user (instead of root) and password that Rails uses. Having them harcoded in database.yml makes it unsafe.

Fortunately we deploy through Capistrano, and there is a recipe for deploying using database credentials previously stored in the target machine. This way, database.yml is not available from source code and credentials remain private.

Everything is available as a gist here ( http://www.simonecarletti.com/blog/2009/06/capistrano-and-database-yml/ ) but I want to complete the information to save you some time.

The recipe is split into 2 tasks:
- generating the database.yml in the target machine (that will end with a cap deploy:setup )
- deploying , but using the database.yml in the target machine (that will end with the usual cap deploy)

Steps:
  1. Download the gist AND save it in /config/capistrano_database_yml.rb
  2. Follow Requirements point in the gist doc (or in the link above)
  3. Follow Usage point in the gist doc (or in the link above)
    1. Note that you must add a require line in your capistrano deploy.rb . This line should be: require "config/capistrano_database_yml" . Since it is searched in your $LOAD path
  4. Create a custom template as in the Custom template section of the gist doc.
    1. In our case we ask for user and password
    2. Save the template in config/deploy/database.yml.erb
    3. There is an error in the doc, at least for Rails 3. To make deploy prompt for user or password, use the usual arb syntax: <%= %> 
    4. We end with something like for the .erb:
      base: &base
        adapter: mysql2
        encoding: utf8
        pool: 5
        host: localhost
        timeout: 5000
        username: <%=Capistrano::CLI.ui.ask("Enter MySQL database username: ")%>
        password: <%=Capistrano::CLI.ui.ask("Enter MySQL database password: ")%>
      development:
        database: my_app_development
        <<: *base
      test:
        database: my_app_test
        <<: *base
      staging:
        database: my_app_staging
        <<: *base
      preproduction:
        database: my_app_preproduction
        <<: *base
      production:
        database: my_app_production
        <<: *base
  5. When the erb is saved. you can run the cap deploy:setup (or cap deploy:setup if you are in a multistage)
    1. this will create a database.yml in your_target_machine /shared/config/database.yml
    2. during this task you were asked for the username and password, and they will be written in the database.yml
    3. revise that the generated database.yml have the right privileges: will be accessible only to  the user you deploy and run teh application with
  6. Once done. You can deploy the application in the usual way. It will use the new database.yml
    1. cap deploy(or cap deploy)
For Jenkins, you must create (or copy) the database.yml to each of the jenkins 'projects'. As it runs test, you only need a database.yml with 'test' section. Revise the access rights of this file also (ex: chmod for Jenkins user only).
The location of databse.yml per project will be(in our case is):
/var/lib/jenkins/jobs//workspace/config/database.yml
 
Profit! 


P.S. As a bonus, take a look to this compilation of capistrano recipes:

https://github.com/webficient/capistrano-recipes


Rails: Log SQL queries to console

This is a quick tip.
Whenever you need to study sql accesses of your code, you can do it during a console session. Just need to configure the logger:

rails c
logger = Logger.new(STDOUT);ActiveRecord::Base.logger = logger;ActiveResource::Base.logger = logger;

 It will log every sql query to the console.

Thursday, September 1, 2011

Atina marido que la noche es corta

Ayer tuvimos un día duro intentando llevar una nueva feature a producción. Cómo siempre, en el último momento es cuando surgen los problemas.

En nuestro caso, tenemos la aplicación principal corriendo en un passenger+nginx, y un demonio que en background va completando tareas pesadas (hace pasadas cada 30s y 15m). El problema vino cuando nos dimos cuenta de que el servidor de preproducción estaba mandando mails cómo si fuera el de producción.

El primer pensamiento fue echar la culpa al demonio: es código hecho in-house. Pero estaba funcionando bien.
Lo siguiente fue revisar linea a linea los ficheros de configuración. Todo OK.
¿el capistrano? Todo correcto. Deployaba con el stage de preproduction.

Finalmente comprobamos que era la aplicación web principal la que estaba corriendo como si fuera producción. Glup!

La solución es tan tonta como modificar la configuración de nuestro nginx de preproduccion y añadir
rails_env preproduction;
bajo el apartado http (así afecta al servidor completo y evitamos futuros problemas). Reiniciar el nginx, y a disfrutar.

La explicación: passenger por defecto corre en el entorno production.