Hace un tiempo en un experimento estábamos construyendo un sitio web para hacer sitios web, la parte compleja de este sitio era que el usuario que creaba su página web quería poner formularios de registros a su antojo y no teníamos como saber cuantos datos y como iba a querer guardarlos. En particular las bases de datos SQL como MySQL y PostgreSQL sufren este problema, necesitas conocer la estructura de antemano para poder guardar datos, pero existe una solución y lo mejor de todo es que está incorporada nativamente dentro de Rails y Postgres y consiste en ocupar la extensión HStore.

Arreglos en PostgreSQL

Para un ejemplo básico vamos a crear un proyecto de Rails desde cero, vamos a crear un modelo FOO con el campo bar, eso si, [symple_highlight color=»red»]es importante estar ocupando postgreSQL en lugar de sqlite [/symple_highlight]

rails g model foo bar:text

Antes de hacer la migración vamos a revisar el archivo de migración generado y modificarlo para especificar que vamos a guardar un arreglo y que por defecto es un arreglo vacío.

 t.text :bar, array: true, default: [] 

Ahora podemos correr las migraciones con

 rake db:migrate 

y podemos entrar al rails console para crear un objeto foo y guardar un arreglo.

Foo.create(bar:[1,2,3,4,5])

ahora el arreglo bar se comparta como un arreglo común de ruby y podemos utilizarlo así:

foo = Foo.create(bar:[1,2,3,4,5])
foo.bar[1]

Hashes en PostgreSQL

Para guardar un diccionario o hash dentro de PostgreSQL primero necesitamos activar la extensión hstore de postgres primero vamos a generar una migración para agregar la columna baz del tipo hstore al modelo de Foo

 rails g migration addBazToFoo baz:hstore 

luego antes de correr la migración tenemos que entrar y modificarle, primero para activar la extensión hstore y luego pare decir que la columna baz ingrese por defecto un hash vacío.

def change
    enable_extension 'hstore'
    add_column :foos, :baz, :hstore, default: {}, null: false
end

luego corremos las migraciones.

 rake db:migrate 

y ahora podemos entrar a la consola de rails para probar los hashs

 rails c 
 foo = Foo.create(baz: {"key" => "value"})
foo.baz["key"] 

Rescatar datos de un hash es sencillo cuando se hace como es el caso anterior, pero que pasa si queremos hacer una consulta en base a uno de los valores dentro del hash, esto también es posible, por ejemplo si queremos rescatar todos los Foo cuyo key del hash tiene un valor especifico también podemos hacerlo.

Para eso voy a repetir el experimento anterior creando el modelo User, con dos campos, name y favs donde en favs vamos a guardar las cosas favoritas del usuario, las cuales no sabemos todavía cuales son (si, podríamos hacer una tabla para los favoritos y realizar una relación de muchos a muchos con la de usuarios, pero lo que queremos hacer ahora es probar el hstore) entonces creamos el modelo de usuarios desde el bash:

rails g model user name:string favs:hstore 

y luego abrir el archivo de la migración el cual debería estar así:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.hstore :favs, default: {}
      t.timestamps null: false
    end
  end
end

(no es necesario habilitar nuevamente el modo hstore, lo hicimos en la migración pasada y se hace sólo una vez) ahora entramos nuevamente a rails console y empezamos a crear los primeros usuarios para hacer las pruebas

User.create(name: "Diego Arias", favs:{"band": "Britney Spears", "movie": "Bridget Jones"})
User.create(name: "Nicolás Melgarejo", favs:{"band": "Madonna", "movie": "Twilight"})
User.create(name: "Juan Cristobal Pazos", favs:{"band": "Shakira", "movie": "Twilight"})

y ahora procedemos a hacer las consultas: Para rescatar a los usuarios cuya banda favorita sea Britney Spears, debemos hacerlo así:

User.where("favs->'band' = ?", "Britney Spears")

Para rescatar a los usuarios cuya película favorita sea Twilight con

User.where("favs->'movie' = ?", "Twilight")

Ahora ocupando hstore y los arrays de Postgres puedes guardar datos sin necesidad de tener una estructura previamente definida, no se aconseja ocuparlo para no modelar el sitio, pero esto es especialmente útil cuando no sabes de antemano que es lo que tendrás que guardar.