Active Record

63
Intro Convenciones Conexi´ on con la DB. Asociasiones Finders Validaciones Callbacks Fin Active Record Sabor Ruby. Gast´ on Ramos - [email protected] 1 / 63 Active Record, sabor Ruby

Transcript of Active Record

Page 1: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Active Record Sabor Ruby.Gaston Ramos - [email protected]

1 / 63

Active Record, sabor Ruby

Page 2: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Acerca de mı:

Soy desarrollador web freenlace.

Programo en Ruby desde hace 2 anos.

Soy miembro de Ruby-AR y Ruby del Litoral.

Publique algunas bibliotecas en ruby.

Colabore con el proyecto RubySpec.

Traduccion del libro ”Rails 2.1 Que hay de Nuevo?”

2 / 63

Active Record, sabor Ruby

Page 3: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Agenda.

1 Intro

2 Convenciones

3 Conexion con la DB.

4 Asociasiones

5 Finders

6 Validaciones

7 Callbacks

8 Fin

3 / 63

Active Record, sabor Ruby

Page 4: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Active Record es un Patron de Diseno.

Basado en el patron ActiveRecord de Martin Fowler (”Patternsof Enterprise Architecture”)

”Un objeto que engloba una fila de una tabla o vista de labase de datos, encapsula el acceso a la base de datos, y agregalogica del dominio del problema sobre estos datos.”

4 / 63

Active Record, sabor Ruby

Page 5: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

La biblioteca de Ruby Active Record .

”Nunca he visto una implementacion de Active Record tancompleta y tan util como la de rails.”

Martin Fowler

5 / 63

Active Record, sabor Ruby

Page 6: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Active Record sigue el standard de ORM.

Active record sigue el standard de ORM y se diferencia de losdemas por que minimiza la cantidad de configuracionmediante el uso de un conjunto de convenciones.

6 / 63

Active Record, sabor Ruby

Page 7: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Active Record sigue el standard de ORM.

Una clase por tabla.

Un objeto por registro.

Las columnas como atributos de estos objetos.

7 / 63

Active Record, sabor Ruby

Page 8: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Convencion sobre configuracion

Don’t Repeat Yourself

8 / 63

Active Record, sabor Ruby

Page 9: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Una clase por Tabla.

Codigo SQL para crear la tabla users:

CREATE TABLE ‘users‘ (‘id‘ int(11) NOT NULL auto_increment,‘login‘ varchar(255) default NULL,‘crypted_password‘ varchar(255) default NULL,‘email‘ varchar(25,5) default NULL,PRIMARY KEY (‘id‘) ) ENGINE=InnoDB

9 / 63

Active Record, sabor Ruby

Page 10: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Una clase por Tabla.

Codigo del modelo en Active Record (Ruby):

class User < ActiveRecord::Baseend

10 / 63

Active Record, sabor Ruby

Page 11: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Uso de convenciones:

- Codigo

- Errores

+ Productividad

11 / 63

Active Record, sabor Ruby

Page 12: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Convencion sobre configuracion.

class User < ActiveRecord::Baseend

Sin archivos XML.

Sin archivos generados.

Cada cosa en un solo lugar.

12 / 63

Active Record, sabor Ruby

Page 13: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Columnas y atributos.

Los objetos de Active Record se corresponden con las filas oregistros de una tabla de la base de datos.

Sin embargo hemos visto que no hay atributos en nuestasdefiniciones de clases.

Esto por que Active Record los determina dinamicamente enruntime.

Active Record ”mira” el esquema dentro de la base de datos yconfigura las clases que mapean las tablas.

13 / 63

Active Record, sabor Ruby

Page 14: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Expresividad en el codigo

14 / 63

Active Record, sabor Ruby

Page 15: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Mucha Informacion en pocas lıneas de codigo

class User < ActiveRecord::Basehas_many :postsbelongs_to :group

validates_presence_of :login, passwordvalidates_uniqueness_of :loginvalidates_confirmation_of :password

end

15 / 63

Active Record, sabor Ruby

Page 16: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Expresividad: Codigo mas bello

+ Motivacion

- Stress

+ Ganas

+ Productividad

16 / 63

Active Record, sabor Ruby

Page 17: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

La biblioteca de Ruby Active Record .

Active Record fue construida para Ruby on Rails, y hace quesea facil el CRUD.

Ya va por la version 2.1.2 y viene con la version 2.1.2 de RoR.(Hace poco salio el RC 2.2)

Create.

Read.

Update.

Delete.

17 / 63

Active Record, sabor Ruby

Page 18: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Tablas y clases.

Por defecto Active Record asume que el nombre de latabla es la forma plural de nombre de la clase.Si el nombre de la clase contiene multiples palabrascapitalizadas, el nombre de la tabla lleva guion bajo entreestas palabras.

18 / 63

Active Record, sabor Ruby

Page 19: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Pero sin no te gusta, lo podes cambiar!.

class Persona < ActiveRecord::Baseset_table_name "persona"

end

19 / 63

Active Record, sabor Ruby

Page 20: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Primary Key.

Active Record asume que cada tabla ha de tener una clave primaria(normalmente llamada id).

Se asegura que este campo id sea unico para cada registro agregadoen la tabla.

Esta es una convencion que puede no gustarle a algunos puristas.

¿Por que debemos usar una clave primariaartificial como id ?

20 / 63

Active Record, sabor Ruby

Page 21: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Primary Key.

La razon es puramente practica!

El formato de los datos externos puedecambiar con el paso del tiempo.

Por ejemplo, tenemos una base de datos con expedientes, el numerodel expediente bien podrıa ser nuestra clave primaria.

¿Que pasa si despues de un ano se decide anteponerle una letra X atodos los numeros de expedientes?

Tendrıamos que cambiar todos los id en nuestro esquema, yactualizar todas las relaciones a la tabla expediente.

Todas estas cosas requieren trabajo.

21 / 63

Active Record, sabor Ruby

Page 22: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Primary Key.

Todos estos problemas desaparecen si usamos nuestro propio valorinterno como primary key.

Si vamos a comenzar un proyecto nuevo trataremos de seguir lasconvenciones, para tener que trabajar menos.

Y si tenemos que trabajar con una base de datos existente podemoscambiar esta convencion.

class Expediente < ActiveRecord::Baseself.primary_key = "nro_expediente"

end

22 / 63

Active Record, sabor Ruby

Page 23: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Primary Key, Composite Key.

Normalmente Active Record se toma el cuidado de crear un nuevovalor de primary key cuando agregamos un nuevo registro a un tabla.

Si nosotros no seguimos la convencion y utilizamos nuestro propiocampo de id, debemos encargarnos de poner un valor de id unicoantes de guardar un nuevo registro.

¿Que pasa con las claves primarias compuestas?

En principio AR no las soporta, pero podemos utilizar algun plugin,http://compositekeys.rubyforge.org/

23 / 63

Active Record, sabor Ruby

Page 24: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Foreign Keys

class User < ActiveRecord::Basebelongs_to :group

end

class Group < ActiveRecord::Basehas_many :users

end

En la tabla user vamos a necesitar un campo group id

tabla id

24 / 63

Active Record, sabor Ruby

Page 25: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Conexion con la Base de Datos

Active Record viene con soporte para DB2, Firebird, Frontbase,MySQL, Openbase, Oracle, Postgres, SQLite, SQL Server, andSybase databases.

Una de las formas de conectarnos a la Base de Datos es mediante eluso del metodo de clase establish connection

Cada conector tiene un pequeno conjunto de parametros conexiondiferente.

25 / 63

Active Record, sabor Ruby

Page 26: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Conexion con la Base de Datos

ActiveRecord::Base.establish_connection(:adapter => "mysql",:host => "rubylit.com.ar",:database => "wiki",:username => "railsuser",:password => "securepw"

)

26 / 63

Active Record, sabor Ruby

Page 27: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Realciones entre tablas

La mayorıa de las aplicaciones que hacemos requieren muchastablas que normalmente estan relacionadas.

En el esquema de base de datos estas relaciones se expresan atraves de claves primarias y claves foraneas.

Es muy de Bajo NivelNecesitamos algo un poco mas comodo

27 / 63

Active Record, sabor Ruby

Page 28: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Realciones entre tablas

Queremos tener la posibilidad de hacer algo como:

name = post.user.name

En vez de:

user_id = post.user_iduser = User.find(user_id)name = user.name

28 / 63

Active Record, sabor Ruby

Page 29: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

belongs_to

has_one

has_many

has_and_belongs_to_many

29 / 63

Active Record, sabor Ruby

Page 30: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

class Project < ActiveRecord::Base

belongs_to :portfoliohas_one :project_manager,

:conditions => "role = ’project_manager’"

has_many :milestoneshas_and_belongs_to_many :categories

end

30 / 63

Active Record, sabor Ruby

Page 31: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

Project#portfolio

Project#project_manager

Project#milestones

Project#categories

31 / 63

Active Record, sabor Ruby

Page 32: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

Project#portfolio = portfolio

Project#portfolio.nil?

Project#project_manager = project_manager,

Project#project_manager.nil?

Project#milestones.empty?

32 / 63

Active Record, sabor Ruby

Page 33: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

Project#milestones.size

Project#milestones << milestone

Project#milestones.delete(milestone)

Project#milestones.find(milestone_id)

Project#milestones.find(:all, options)

33 / 63

Active Record, sabor Ruby

Page 34: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Asociaciones

Project#milestones.create

Project#categories.empty?

Project#categories.size

Project#categories << category1

Project#categories.delete(category1)

34 / 63

Active Record, sabor Ruby

Page 35: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Uno a Uno

class Employee < ActiveRecord::Basehas_one :office, :dependent => :destroy

end

class Office < ActiveRecord::Basebelongs_to :employee

end

35 / 63

Active Record, sabor Ruby

Page 36: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

One to Many

class Manager < ActiveRecord::Basehas_many :employees, :dependent => :nullify

end

class Employee < ActiveRecord::Basebelongs_to :manager

end

36 / 63

Active Record, sabor Ruby

Page 37: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Muchos a muchos

Hay dos formas de construir una relacion de muchosa muchos, la primer forma usa has many con laopcion :through y un modelo de union.

37 / 63

Active Record, sabor Ruby

Page 38: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Muchos a muchos

class Assignment < ActiveRecord::Basebelongs_to :programmer # foreign key - programmer_idbelongs_to :project # foreign key - project_id

end

class Programmer < ActiveRecord::Basehas_many :assignmentshas_many :projects, :through => :assignments

end

class Project < ActiveRecord::Basehas_many :assignmentshas_many :programmers, :through => :assignments

end

38 / 63

Active Record, sabor Ruby

Page 39: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Muchos a muchos

La segunda forma usa has and belongs to many en ambosmodelos.

# foreign keys en la tabla de union

class Programmer < ActiveRecord::Basehas_and_belongs_to_many :projects

end

class Project < ActiveRecord::Basehas_and_belongs_to_many :programmers

end

39 / 63

Active Record, sabor Ruby

Page 40: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Find es el metodo principal en AR.

User.find(1)#<User id: 1, name: "Pablo", login: "gaston", password: "pepe">

# SQL ejecutado:

SELECT * FROM ‘users‘ WHERE (‘users‘.‘id‘ = 1)

# ---------------------------------------------------------

User.first#<User id: 1, name: "Pablo", login: "gaston", password: "pepe">

# SQL ejecutado:

SELECT * FROM ‘users‘ LIMIT 1

40 / 63

Active Record, sabor Ruby

Page 41: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Find es el metodo principal en AR.

User.all

[#<User id: 1, name: "Pablo", login: "gaston", password: "pepe">,#<User id: 2, name: "David Bner", login: "david", password: "ppp">,#<User id: 3, name: "Pepito", login: "pepe", password: "1234">]

# SQL ejecutado:

SELECT * FROM ‘users‘

41 / 63

Active Record, sabor Ruby

Page 42: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Find con condiciones.

User.find(:all,:conditions => {:name => "david"}

)# SQL ejecutado:

SELECT * FROM ‘users‘ WHERE (‘users‘.‘name‘ = ’david’)

# ---------------------------------------------------------

User.find(:all,:conditions =>{ :first_name => "Bruce",

:last_name => "Lee" } )# SQL ejecutado:

SELECT * FROM users WHERE(first_name =’Bruce’ and last_name = ’Lee’);

42 / 63

Active Record, sabor Ruby

Page 43: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Order by.

User.find(:all, :order => "name desc")

[#<User id: 3, name: "Pepito", login: "pepe", password: "1234">,#<User id: 1, name: "Pablo", login: "gaston", password: "pepe">,#<User id: 2, name: "David Bner", login: "david", password: "ppp">]

# SQL ejecutado ---------------------------------

Select * from Users ORDER BY name desc;

43 / 63

Active Record, sabor Ruby

Page 44: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Joins.

User.find(:all,:join => "LEFT JOIN comments ON

comments.post_id = id")

# ---------------------------------------------------

SQL ejecutado:

SELETC * FROM Users LEFT JOIN commentsON comments.post_id = id;

44 / 63

Active Record, sabor Ruby

Page 45: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Creacion.

user = User.new(:name => "David",:occupation => "Code Artist")

user.save

user.name # => "David"

# ----------------------------------------------

User.create(:name => "Pepito", :login => "pepe",:password => "1234")

45 / 63

Active Record, sabor Ruby

Page 46: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Creacion.Creacion de un nuevo objeto pasandole un bloque con ladescripcion de sus atributos

User.create(:first_name => ’Jamie’) do |u|u.is_admin = false

end

Creacion de un array de objetos nuevos usando un bloque. Elbloque se ejecuta una vez por cada nuevo objeto creado.

User.create([{:first_name => ’Jamie’},{:first_name => ’Jeremy’}]) do |u|

u.is_admin = falseend

46 / 63

Active Record, sabor Ruby

Page 47: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Creacion.

Esto tambien funciona para las asociaciones:

author.posts.create!(:title => "New on Edge") {|p|p.body = "More cool stuff!"

}

47 / 63

Active Record, sabor Ruby

Page 48: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

El metodo sum.

Acepta expresiones a partir de la version 2.1

Product.sum("price * 2")

# ---------------------------------------------------

SQL ejecutado:

SELECT sum(price * 2) AS sum_price_all_2 FROM ‘products‘

# ---------------------------------------------------

A partir de la version Rails 2.1 el valor de retorno por defecto (cuando nose encuentra ningun registro) es 0.

Account.sum(:balance, :conditions => ’1 = 2’)#=> 0

48 / 63

Active Record, sabor Ruby

Page 49: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

El metodo count.

Person.count(:conditions => "age > 26")

Person.count(’id’, :conditions => "age > 26")# Hace un COUNT(id)

Person.count(:conditions => "age > 26 AND job.salary > 60000",:joins => "LEFT JOIN jobs on jobs.person_id = person.id")

Person.count(:all, :conditions => "age > 26")# Hace un COUNT(*)

49 / 63

Active Record, sabor Ruby

Page 50: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Otros Finds

Finds alternativos:

User.find_by_sql("SELECT * from users")

User.find_by_name_and_login("David", "dhh")

User.find_or_create_by_name(’Bob’, :age => 40)

50 / 63

Active Record, sabor Ruby

Page 51: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Y mas... Finds

Finds, like, select

User.find( :all,:conditions => ["name like ?" , "#{name}%" ])

Talks.find( :all,:select => "title, speaker, recorded_on" )

51 / 63

Active Record, sabor Ruby

Page 52: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Validaciones

Active Record puede validar el contenido de objeto del modelo.

Estas validaciones se realizan automaticamente cuando el objeto segraba en la BD.

Si las validaciones fallan el objeto no se guarda y queda en memoriacon un estado invalido.

Active Record puede distinguir entre objetos que corresponden aregistros en la BD y los que no.

User.save User.new_record?

Update o insert segun cada caso.

52 / 63

Active Record, sabor Ruby

Page 53: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Validaciones

validate

(en cada operacion de grabado)

validate_on_create

validate_on_update

User.valid?

(lo podes consultar en cualquier momento)

53 / 63

Active Record, sabor Ruby

Page 54: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Validation Helpers

Active Record tiene un conjunto de metodos ”helpers” que agreganvalidaciones a nuestros modelos.

El nombre no puede estar vacıo.

La edad debe ser entre 18 y 90 anos, etc.

Estas validaciones ”comunes” las hacen los helpers.

54 / 63

Active Record, sabor Ruby

Page 55: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Validation Helpers

validates_format_of

validates_uniqueness_of

validates_acceptance_of

validates_associated

validates_confirmation_of

validates_exclusion_of

validates_inclusion_of

validates_length_of

validates_numericality_of

55 / 63

Active Record, sabor Ruby

Page 56: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Ejemplos de Validation Helpers

class User < ActiveRecord::Base

validates_confirmation_of :password

end

class User < ActiveRecord::Base

validates_length_of :password, :in => 6..20

validates_length_of :address, :minimum => 10,:message => "seems too short"

end

56 / 63

Active Record, sabor Ruby

Page 57: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Callbacks

Usando callbacks, Active Record te permite ”pariticipar” en esteproceso de monitoreo.

Con los callbacks podemos escribir codigo se invocara en cadaevento significante del objeto.

Active Records define 20 callbacks.

Por ejemplo before destroy que se ejecuta antes de que el metododestroy se llame.

57 / 63

Active Record, sabor Ruby

Page 58: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

model.save()

Nuevo registro

before validation

before validation on create

after validation

before save

before create

Insert

after create

after save

58 / 63

Active Record, sabor Ruby

Page 59: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

model.save()

Registro existente

before validation

before validation on update

after validation

after validation on update

before save

before update

Update

after update

after save

59 / 63

Active Record, sabor Ruby

Page 60: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

model.destroy()

before destroy

Delete

after destroy

60 / 63

Active Record, sabor Ruby

Page 61: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Algunas cosas nuevas de la version 2.1

Named scope

Query cache

Dirty attributes

61 / 63

Active Record, sabor Ruby

Page 62: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Final

Preguntas?

62 / 63

Active Record, sabor Ruby

Page 63: Active Record

Intro Convenciones Conexion con la DB. Asociasiones Finders Validaciones Callbacks Fin

Final, Referencias

Fin, Gracias por escucharReferencias:

http://ar.rubyonrails.com/

Agile Web Development with Rails - Second Edition

ISBN: 0-9776166-3-0

Mi blog acerca de Ruby:http://gastonramos.wordpress.com

63 / 63

Active Record, sabor Ruby