Monday, August 06, 2007

Versionado de tablas con Ruby on Rails


En este post voy a mostrar un ejemplo de como implementar versionado de una tabla con Ruby On Rails.



El problema que estamos atacando es el siguiente: tenemos un ABM sobre una tabla y queremos que cada vez que se realice una modificación sobre algún registro, la versión anterior a la modificada quede guardada y podamos en caso necesario revertir nuestros cambios y volver a esa versión del registro (o a cualquier version anterior).



Para eso vamos a utilizar un plugin de Rails llamado acts_as_versioned



Empezamos creando la aplicacion rails



rails versiones
cd versiones



A continuación instalamos el plugin. Para eso primero actualizamos la lista de plugins disponibles



ruby script/plugin discover ruby



Y después instalamos el plugin que nos interesa



script/plugin install acts_as_versioned



El siguiente paso es la configuración de la base de datos. No vamos a usar nada específico de ningún motor de base de datos, asi que no voy a explicar en detalle como hacerlo. Aquí hay una explicación detallada de instalación y configuración con MySQL



A continuación generamos el modelo para nuestro ejemplo de versionado, que va a ser una tabla de clientes



ruby script/generate model Cliente



Esto nos genera una serie de archivos, entre los cuales esta el archivo de migración que utilizaremos para crear nuestra tabla . Completamos este script de migracion con la estructura de nuestra tabla de clientes:



db/migrate/001_create_clientes.rb



class CreateClientes < ActiveRecord::Migration

    def self.up

     create_table :clientes do t

      t.column :nombre,:string

      t.column :apellido,:string

      t.column :direccion,:string

     end

    end



    def self.down

     drop_table :clientes

    end

end





Ejecutamos la migración



rake db:migrate



Y generamos el controller y las views necesarias para la edicion de nuestra tabla



script/generate controller cliente list edit



Completamos el controller y las views que generamos con el siguiente código, para tener la posibilidad de ver y editar los registros de nuestra tabla (no voy a entrar en explicaciones con esta parte porque no tiene que ver directamente con el tema de versionado y además está abundantemente documentado)



app/controller/cliente_controller.rb



class ClienteController < ApplicationController 

  def list 

   @clientes = Cliente.find(:all)

  end 



  def edit 

   @cliente = Cliente.find(params[:id])

  end 



  def change 

   @cliente = Cliente.find(params[:id])

   if(@cliente.update_attributes(params[:cliente])) then 

    redirect_to(:action => 'list')

   else 

    render(:action => 'edit')

   end 

  end 

end





app/view/cliente/list.rhtml



  <Table>

   <% @clientes.each do cliente %>

    <TR>

     <TD<%= cliente.nombre%> </TD>

     <TD<%= cliente.apellido%> </TD>

     <TD<%= cliente.direccion%> </TD>

     <TD<%= link_to 'Edicion', {:action => 'edit',:id => cliente}%> </TD>

    </TR>

   <%end%>

  </Table>





app/views/cliente/edit.rhtml



  <%= start_form_tag({:action => 'change', :id => @cliente}, :id => "form") %>

   <%= error_messages_for 'cliente' %>



   <p><label for="cliente_nombre">Nombre:</label><br/>

   <%= text_field 'cliente', 'nombre'  %></p>



   <p><label for="cliente_apellido">Apellido:</label><br/>

   <%= text_field 'cliente', 'apellido'  %></p>



    <p><label for="cliente_direccion">Direccion:</label><br/>

    <%= text_field 'cliente', 'direccion'  %></p>



    <%= submit_tag "Salvar Cambios" %>

   <%= end_form_tag %>





Con esto tenemos funcional una aplicación que nos permite listar y editar nuestros clientes. Vamos a generar algunos registros con una migracion para poder probarla:



ruby script/generate migration crea_clientes_prueba






class CreaClientesPrueba < ActiveRecord::Migration

  def self.up

   (1..20).each do |index|

     Cliente.new(:nombre => sprintf("Nombre_%d",index),

                 :apellido => sprintf("Apellido_%d",index),

                 :direccion => sprintf("Direccion_%d",index)).save!

   end

  end



  def self.down

  end

end





rake db:migrate



Levantamos el server:



ruby script/server



y, apuntando un browser a http://localhost:3000/cliente/list , debería aparecer el listado de clientes.



Ahora vamos a modificar este ABM para que maneje versiones. En primer lugar indicaremos que nuestro modelo es versionado.



app/model/cliente.rb



  class Cliente < ActiveRecord::Base

   acts_as_versioned

  end





Generamos la tabla en la que se guardaran las diferentes versiones, nuevamente con una migración.



script/generate migration crea_tabla_versiones



Completamos el contenido del archivo de migracion






  class CreaTablaVersiones < ActiveRecord::Migration

     def self.up

        Cliente.create_versioned_table

     end



     def self.down

        Cliente.drop_versioned_table

     end

  end





Los métodos create_versioned_table y drop_versioned_table se agregan automáticamente al modelo cuando declaramos que este es versionado (cuando decimos acts_as_versioned) que generan y borran la tabla auxiliar necesaria para guardar las versiones de los registros de la tabla principal



Ejecutamos la migración para crear la tabla auxiliar



rake db:migrate



Y ya está, nuestros clientes tienen múltiples versiones. Para ver como funciona esto y algunos ejemplos, vamos a recurrir a la consola de rails (que es un excelente recurso cuando queremos jugar con nuestro modelo sin escribir todavía la interfaz gráfica).



ruby script/console






 #Creamos un cliente y lo salvamos

 >> cliente = Cliente.new(:nombre => "Homero",

                          :apellido => "Simpson",

                          :direccion => "Avenida Siempreviva 742, Springfield")

 >> cliente.save!





 #En que version estamos?

 >> cliente.version

 => 1



 #Ahora cambiamos la direccion y salvamos los cambios:

 >> cliente.direccion = "430 Camino Spalding"

 => "430 Camino Spalding"

 >> cliente.save!

 => true



 #En que version estamos?

 >> cliente.version

 => 2



 #Accedemos a los datos de las distintas versiones

 >> cliente.versions[0].direccion

 => "Avenida Siempreviva 742, Springfield" 

 >> cliente.versions[1].direccion

 => "430 Camino Spalding"

 #Y deshacemos los cambios para volver a la versión anterior

 >> cliente.direccion

 => "430 Camino Spalding"

 >> cliente.revert_to(1)

 => true 

 >> cliente.direccion

 => "Avenida Siempreviva 742, Springfield" 



 >> cliente.save!

 => true



 #Es interesante ver que el hecho de volver a la version anterior no eliminó la versión intermedia

 >> cliente.version

 => 3

 >> cliente.versions[0].direccion

 => "Avenida Siempreviva 742, Springfield"

 >> cliente.versions[1].direccion

 => "430 Camino Spalding"

 >> cliente.versions[2].direccion

 => "Avenida Siempreviva 742, Springfield"





Ahora que ya probamos esto desde la consola (y no crean que las direcciones son al azar) ,vamos a agregar la información a la interfaz gráfica. En primer lugar vamos a agregar al listado de clientes el numero de version y una opcion que nos permita volver a una version anterior.






  <Table>

   <% @clientes.each do cliente %>

    <TR>

     <TD<%= cliente.nombre%> </TD>

     <TD<%= cliente.apellido%> </TD>

     <TD<%= cliente.direccion%> </TD>

     <TD<%= cliente.version%> </TD>

     <TD<%= link_to 'Versiones', {:action => 'versiones',:id => cliente}%> </TD>

     <TD<%= link_to 'Edicion', {:action => 'edit',:id => cliente}%> </TD>

    </TR>

   <%end%>

  </Table>





Agregamos en el controller el codigo para obtener las versiones anteriores:






 def versiones

  @cliente = Cliente.find(params[:id])

  @versiones_cliente = @cliente.versions

 end





Y creamos una view versiones.rhtml que nos permita ver las versiones anteriores:






 <Table>

  <% @versiones_cliente.each_with_index do version,index %>

   <TR>

    <TD<%= version.nombre%> </TD>

    <TD<%= version.apellido%> </TD>

    <TD<%= version.direccion%> </TD>

    <TD<%= link_to 'Volver a', {:action => 'volver_a_version',:id =>@cliente,:version => index+1}%> </TD>

   </TR>

  <%end%>

 </Table>





Con estos agregados podemos ver facilmente las versiones anteriores de un cliente.



Ahora nos falta implementar la vuelta a una versión anterior. Como se puede ver, en el listado de versiones ya incluimos un link que nos permite volver a cada version. Solo nos falta incluir en el controller la acción correspondiente a esa vuelta atrás:






   def volver_a_version

       @cliente = Cliente.find(params[:id])

       @version = params[:version]

       @cliente.revert_to(@version.to_i)

       @cliente.save!

       redirect_to(:action => 'list')

    end



0 Comments:

Post a Comment

<< Home