Sei sulla pagina 1di 41

Asociaciones en Rails!

Ingeniera de Sistemas de
Informacin!
Grado en Ingeniera en Tecnologas de
Telecomunicacin!
GSyC!

2012 Departamento GSyC, URJC!


Algunos derechos reservados. Este trabajo se
distribuye bajo la licencia Creative Commons
Attribution-NonCommercial-ShareAlike 3.0
Unported License!
2012 Armando Armando Fox & David
Patterson!
Licensed under Creative Commons AttributionNonCommercial-ShareAlike 3.0 Unported License!

GSyC!

2!

Asociaciones y Claves
forneas
(ELLS 7.3)!

Asociaciones en Rails!
Sirven para modelar las relaciones lgicas entre dos
tipos de entidades en una arquitectura SW!
ActiveRecord::Associations proporciona un conjunto de
mtodos de clase que generan mtodos proxy!
Los mtodos proxy generados ligan instancias de
modelos que estn relacionadas entre s a travs de
claves forneas de sus respectivas tablas en la BD!
Hacen ms natural y fcil la creacin, modificacin y
eliminacin de modelos relacionados/asociados a travs
de claves forneas en las tablas de la BD sin tener que
usar operadores relacionales!

GSyC!

4!

Ejemplo: queremos aadir


crticas/reviews a rottenpotatoes!
Hasta ahora tenemos dos modelos, Movie y
Moviegoer
Y cada uno corresponde con una tabla de la BD
relacional: movies y moviegoers
Ahora queremos que los moviegoers puedan criticar
las movies!
Cada review tendr asociados dos atributos descriptivos: una
puntuacin (# de potatoes) y un comentario!

Cada moviegoer puede escribir 0 o ms reviews!


Cada movie podr tener 0 o ms reviews!
Cada review pertenece a un moviegoer y a una movie!
No puede existir una review que no pertenezca a un
moviegoer y a una movie!
GSyC!

5!

Diagrama Entidad/Relacin!
name

provider

uid

potatoes
comments

id

id
Moviegoer

rating

Reviews

release_date

title description

Movie

Las entidades Moviegoer y Movie mantienen


entre s una relacin, Reviews, que es muchosa-muchos!
Un Moviegoer puede criticar 0 o muchas movies!
Una Movie puede ser criticada por 0 o muchos
moviegoers!
GSyC!

6!

Modelo relacional. Diagrama ~UML!

Un Moviegoer tiene 0 o ms reviews (*)!


1..n significara al menos una review!
Una Movie tiene 0 o ms reviews (*)!

1..n significara al menos una review!

Una Review pertenece a un slo Moviegoer


(1)!

Al pasar del modelo E/R al modelo relacional, en


Rails utilizaremos un nuevo modelo/tabla
Review para modelar la relacin Reviews!

0..1 significara que puede haber reviews que no


pertenecen a ningn Moviegoer!

Decimos que cada Moviegoer has many


reviews!
Decimos que cada Movie has many reviews!
Decimos que cada Review belongs to una
Movie y un Moviegoer!

Una review pertenece a una sola Movie!

0..1 significara que puede haber reviews de


pelculas que no estn en la BD!

GSyC!

7!

Modelo Relacional!

En el modelo relacional las relaciones entre tablas se expresan con claves forneas
(FK, foreign keys)!
Una columna de una tabla A que contiene la foreign key de una tabla B contiene
claves primarias de filas(objetos) de la tabla B!
Sirve para relacionar cada fila(objeto) de la tabla A con una fila(objeto) de la tabla B!
Recuerda que en Rails las migraciones crean tablas cuya columna de clave primaria
se llama id!

reviews

movies
id!

title!

rating!

13! Inception!

PG-13!

41! Star Wars!

PG!

id!

movie_id!

moviegoer_id!

potatoes!

21!

41!

1!

5!

22!

13!

2!

3!

23!

13!

1!

4!

moviegoers

43! Its Complicated! R!


GSyC!

id!

name!

1!

alice!

2!

bob!

3!

carol!
8!

Operaciones relacionales!

Se pueden expresar y manipular


relaciones complejas usando operaciones
de lgebra relacional que operan sobre
una coleccin de tablas!
Ejemplo: todas las reviews de la pelcula
cuyo id es 13!
Producto cartesiano de todas las filas de movies y de reviews (9 filas, 7
columnas)
Nos quedamos con las que tengan movies.id = reviews.movie_id (slo 2 filas de
las anteriores)
Nos quedamos con las que tengan movies.id=13 (slo una fila de las
anteriores)
GSyC!

9!

tabla movies'

Operaciones relacionales!
tabla 'reviews'

id

title

id

potatoes

movie_id

13

Inception

21

41

41

Star Wars

22

13

43

Its complicated

23

13

Producto cartesiano: movies JOIN reviews


movies.id

movies.title

reviews.id

reviews.potatoes reviews.movie_id

13

Inception

21

41

13

Inception

22

13

13

Inception

23

13

41

Star Wars

21

41

41

Star Wars

22

13

41

Star Wars

23

13

43

Its complicated

21

41

43

Its complicated

22

13

43

Its complicated

23

13

Producto cartesiano filtrado: movies JOIN reviews ON movies.id = reviews.movie_id


movies.id

movies.title

reviews.id

reviews.potatoes reviews.movie_id

13

Inception

23

13

41

Star Wars

21

41

movies JOIN reviews ON movies.id = reviews.movie_id where movies.id = 13


movies.id
13

movies.title
Inception

reviews.id
23

reviews.potatoes reviews.movie_id
4

GSyC!

13

10!

Operaciones relacionales!

Se pueden expresar y manipular


relaciones complejas usando operaciones
de lgebra relacional que operan sobre
una coleccin de tablas!
Ejemplo: todas las reviews de la pelcula
cuyo id es 13:!
sqlite3 -line db/development.sqlite3
"SELECT reviews.* FROM movies JOIN
reviews ON movies.id=reviews.movie_id
WHERE movies.id=13"
GSyC!

Producto
cartesiano!

11!

Mapping Object=>Relacional!
En lugar de tener que escribir consultas en SQL y ser conscientes
de la existencia de las tablas y las FK, nos gustara manejar objetos
Ruby:!

Una movie has_many reviews!


Una review belongs_to una pelcula!
Un moviegoer has_many reviews!
Una review belongs_to un moviegoer !

inception = Movie.find_by_title('Inception')
alice=Moviegoer.find(alice_id)
bob=Moviegoer.find(bob_id)

http://pastebin.com/NcezVx7R

alice_review = Review.new(:potatoes => 5, :comments => "Magnfica")


bob_review
= Review.new(:potatoes => 2, :comments => "Pasable")
inception.reviews = [alice_review, bob_review]
inception.save! # No modifica movies sino reviews para poner movie_id == inception.id
alice.reviews << alice_review
alice.save! # No modifica moviegoers sino reviews para poner moviegoer_id == alice.id
bob.reviews << bob_review
bob.save!
# No modifica moviegoers sino reviews para poner moviegoer_id == bob.id
# nombres de los moviegoers que han criticado inception
inception.reviews.map { |r| r.moviegoer.name } # => ['alice','bob']

GSyC!

12!

Mdulo
ActiveRecord::Associations!
has_many, has_one, belongs_to son llamadas a mtodos de
ActiveRecord::Associations!
Estos mtodos de clase utilizan metaprogramacin para generar
mtodos proxy nuevos en el modelo que permiten atravesar las
asociaciones, construyendo las queries adecuadas para la BD de
manera transparente al programador!
Forman parte del mapping Object=>Relational que permite que el
programador trabaje en trminos OO y no en trminos del modelo
relacional!
Asocian modelos, permitiendo manipular las relaciones del modelo
relacional implementadas en las tablas con PK, FK, de un modo ms
Ruby: objetos que contienen objetos: review.moviegoer,
review.movie, moviegoer.reviews, movie.reviews
Tras llamar a has_many, has_one, belongs_to, casi no hay que pensar
en trminos de PK, FK, de joins, de relaciones/tablas!
OJO!: el programador tiene que aadir las FK en la migracin!
GSyC!

13!

La idea!
Gracias a que la tabla reviews tiene un

campo foreign key (FK) que contiene la


primary key de la Movie
Y a haber usado has_many y belongs_to!
cuando se acceda a movie.reviews se
realizar un join en la BD para encontrar las
reviews en las que movie_id == movie.id
y cuando se acceda a review.movie se
busca en la tabla movies la peli cuya PK id
== review.movie_id
GSyC!

14!

Migracin para aadir FK!


rails generate migration create_reviews
Y luego editamos la migracin:!
class AddReviews < ActiveRecord::Migration
def self.up
create_table :reviews do |t|
t.integer
'potatoes
t.text
'comments'
t.references 'movie'
t.references 'moviegoer
t.timestamps
end
end
def down ; drop_table 'reviews' ; end
end

http://pastebin.com/huJagzRZ

t.references 'movie' crea el campo FK movie_id


t.references 'moviegoer' crea el campo FK moviegoer_id
GSyC!

15!

Asociaciones entre modelos!


Asociaciones entre modelos!
class Review < ActiveRecord::Base
belongs_to :movie
belongs_to :moviegoer
end

http://pastebin.com/GBTCH47f

class Movie < ActiveRecord::Base


has_many :reviews

end
class Moviegoer < ActiveRecord::Base
has_many :reviews

end

has_one es como has_many, pero slo puede poseer uno!


GSyC!

16!

Mtodos proxy de la
Association!
Ahora ya podemos escribir cdigo ms OO para
manipular la BD relacional:

@movie.reviews # Enumerable

Y en el otro sentido:
@review.movie

Se pueden aadir reviews a un peli:!


@movie = Movie.find_by_title('Fargo)
@movie.reviews.build(:potatoes => 5)
@movie.reviews.create(:potatoes => 2)
@movie.reviews << @new_review
@movie.reviews.find(:first,:conditions => '...')

GSyC!

17!

Mtodos proxy creados por


has_many, belongs_to
Siendo m una movie:!
m.reviews
Enumerable de todas las reviews que posee m!
m.reviews=[r1, r2]
Reemplaza en la BD las reviews actuales, si existen, por r1 y r2, actualizando
r1.movie_id = m.id y r2.movie_id = m.id
m.reviews<<r2
Aade a la BD r2, poniendo r2.movie_id = m.id
r = m.reviews.build(:potatoes => 5)
Construye en memoria nueva review, con r.movie_id = m.id
m.save
Guarda m y sus reviews
r = m.reviews.create(:potatoes => 5)
Construye en la BD nueva review, con r.movie_id = m.id
m = r.movie
devuelve la pelcula a la que pertenece la review r
r.movie = m
reemplaza en la BD la FK de la peli a la que pertenece la review r por m.id
GSyC!

18!

Cmo funciona?!
Los modelos que participan en una asociacin tienen que
tener un atributo con la FK del objeto al que pertenecen!
e.g., movie_id in reviews table!

ActiveRecord manipula este campo tanto en la BD como


en el objeto Ruby AR del modelo en memoria!
NO lo tienes que manipular t!!
Ojo: tienes que adaptarte a los convenios de Rails en cuanto a
nombre de tablas, clave primaria surrogada id, claves forneas en
la tabla del modelo que tiene belongs_to. !
Ejemplo: si en el modelo Review aparece belongs_to :movie
entonces en la migracin de la tabla reviews tiene que aparecer la
FK como t.references movie

GSyC!

19!

Resumen/receta: para aadir


una asociacin uno-a-muchos
(one-to-many) en RoR!
1. Aadir has_many al fichero del modelo que posee
y belongs_to en el lado del modelo posedo!
2. Crear la migracin para aadir la FK al lado
posedo para que referencie al que le posee!
3. Aplicar la migracin!
4. rake db:test:prepare para regenerar el
esquema de la BD de test!

GSyC!

20!

Integridad referencial!
Qu ocurre si borramos una pelcula de la que existen crticas?!
El campo FK movie_id de sus crticas en la tabla reviews referencia una
PK que ya no existe. !
Esta es una razn por la que las PK nunca se reciclan en una tabla: sera
desastroso si una nueva pelcula adquiriese la misma clave primaria que la
antigua pelcula!

Podemos optar por alguna de estas alternativas:!


Borrar las crticas de una peli cuando se borra esa peli

has_many :reviews, :dependent => :destroy


Dejar las crticas hurfanas (sin dueo)!

has_many :reviews, :dependent => :nullify


Se pueden hacer otras cosas, usando las callbacks del ciclo de vida
del modelo explcitamente (ej: mezclando)!
A la hora de crear una review tambin podemos controlar la integridad
referencial: se puede impedir que se creen reviews de pelculas que
no existan en la tabla movies con una validacin en el modelo Review:
validate_presence_of :movie
GSyC!

21!

Test RSpec para comprobar la


integridad referencial!
it "should destroy reviews when movie deleted" do
@movie = @movie.create!(...)
@review = @movie.reviews.create!(...)
review_id = @movie.reviews[0].id
@movie.destroy
lambda { review.find(review_id) }.should
raise_error(ActiveRecord::RecordNotFound)
end

GSyC!

22!

Asociaciones mltiples
muchos-a-muchos / many-to-many
con has_many X :through Y
y has_and_belongs_to_many X

Muchos a muchos!

moviegoer: has_many :reviews


movie: has_many :reviews
review: belongs_to :moviegoer
belongs_to :movie

Cmo obtener todas las movies criticadas por


un moviegoer? !

GSyC!

24!

Asociaciones muchos-a-muchos!
Escenario: Los moviegoers critican movies!
un moviegoer puede tener
muchas crticas!
pero una movie tambin puede tener muchas
crticas!

Movie tiene una relacin muchos-amuchos indirecta, a travs de Review, con


Moviegoer!
Una Movie es criticada por muchos Moviegoers!
Un Moviegoer critica muchas Movies !
GSyC!

25!

Asociaciones muchos-a-muchos!
Cmo obtener todas las movies criticadas
por un moviegoer? !
Podemos hacerlo usando las asociaciones
actuales entre Movie y Moviegoer:!
m.reviews.map { |r|

r.moviegoer.name }

Pero podemos simplificarlo con


has_many X :through Y

GSyC!

26!

has_many :through

moviegoer: has_many

:reviews
has_many :movies, :through => :reviews

movie: has_many

:reviews

has_many :moviegoers, :through => :reviews!

reviews: belongs_to

:moviegoer
belongs_to :movie
GSyC!

27!

Through!
Ahora podemos escribir esto:!
@user.movies # movies rated by user
@movie.moviegoers # users who rated this
movie

GSyC!

28!

Through: join de 3 tablas!


En SQL, suponiendo que @user.id == 1, la
consulta @user.movies habra que
realizarla as:
sqlite3 -line db/development.sqlite3
SELECT movies.*
FROM movies JOIN reviews ON movies.id = reviews.movie_id
JOIN moviegoers ON reviews.moviegoer_id = moviegoer.id
WHERE moviegoers.id = 1

GSyC!

29!

Through: OJO!
Nunca deberamos hacer esto:!
alice = Moviegoer.find_by_name(Alice)
alice.movies << Movie.find_by_name
(Inception)
Dado que movies es un has_many through reviews, creara
una review con potatoes y comments a nil (qu si no?)
para ligar la moviegoer alicia con la pelcula!
Podramos evitarlo aadiendo validates_presence_of :comments
y validates_presence_of :potatoes al modelo Review

GSyC!

30!

Has_and_belongs_to_many!
En una relacin many-to-many en la que no hay atributos
descriptivos en la relacin, para qu queremos el modelo
Review?!
Ejemplo: moviegoers likes movies!
Queremos poder saber las pelculas que le gustan a un moviegoer, y
todos los moviegoers a los que les gusta una pelcula!
@a_moviegoer.movies!
@a_movie.moviegoers!

Al no tener un modelo intermedio Y no podemos escribir en


Movie has_many :moviegoers, :through => Y, ni en
Moviegoer has_many :movies, :through => Y !
Para este caso podemos usar
has_and_belongs_to_many en ambos modelos!
Pero seguimos necesitando crear la tabla de la relacin, que
ha de llamarse !
GSyC!

31!

Has_and_belongs_to_many!
El nombre de la tabla ha de ser la concatenacin de los nombres de las
tablas de las entidades, en orden lexicogrfico y separados por un
underscore:!
Movies y moviegoers => moviegoers_movies!
En la migracin se incluyen los campos de las foreign keys, y se requiere
explcitamente que no se aada campo de clave para esta tabla:!
class CreateLikesJoinTable < ActiveRecord::Migration
def change
create_table :moviegoers_movies, :id => false do |t|
t.references :moviegoer
t.references :movie
end
end
end

Los modelos incluyen ambos has_and_belongs_to_many


class Moviegoer < ActiveRecord::Base
has_and_belongs_to_many :movies
end

class Movie < ActiveRecord::Base


has_and_belongs_to_many :moviegoers
end

GSyC!

32!

El equivalente a las vistas:


:class_name, :source,
:conditions, :order

Podemos crear el equivalente


a las vistas de la BD:!
class movie < ActiveRecord::Base
has_many :reviews
has_many :recent_reviews,
:class_name => 'Review',
:conditions =>['created_at > ?', "#{1.week.ago}"]
has_one :latest_review,
:class_name => 'Review',
:order => 'created_at DESC
has_many :with_bad_reviews,
:class_name => "Review",
:conditions => ['reviews.potatoes < ?', "3"]
# Otro nombre para has_many :moviegoers, :through => Review
has_many :reviewers, :through => :reviews,
:source => :moviegoer
# Otro nombre para has_and_belongs_to_many :moviegoers
has_and_belongs_to_many :fans,
:class_name => "Moviegoer
end

GSyC!

34!

Cmo afectan las


asociaciones a los
controladores y las vistas
(ELLS 7.4)!

Rutas RESTful anidadas!


En el momento de crear una review hay que
ligarla a un id de Moviegoer y a uno de Movie!
BDD: cuando estamos en show movie details
pulsamos en un botn para ir al formulario de
crear review!
Podramos guardar en session el id de la peli
que estamos viendo!
Luego, en el formulario de crear la review,
recuperamos de session el id de la peli!
El moviegoer_id lo tenemos en @current_user!
Pero esta forma de pasar el id de movie no es
RESTful!
GSyC!

36!

Rutas RESTful anidadas!


Podemos reflejar en las URLs la asociacin
entre movies y reviews!
En config/routes.rb cambiamos
resources :movies
por
resources :movies do
resources :reviews
end

Es una ruta anidada (nested route)!


Significa que no puedes acceder a reviews sin
nombrar la pelcula a la que se refiere la review!
GSyC!

37!

Rutas RESTful anidadas!


! rake routes:!
movie_reviews GET
POST
new_movie_review GET
edit_movie_review GET
movie_review GET
PUT
DELETE

/movies/:movie_id/reviews(.:format)
/movies/:movie_id/reviews(.:format)
/movies/:movie_id/reviews/new(.:format)
/movies/:movie_id/reviews/:id/edit(.:format)
/movies/:movie_id/reviews/:id(.:format)
/movies/:movie_id/reviews/:id(.:format)
/movies/:movie_id/reviews/:id(.:format)

reviews#index
reviews#create
reviews#new
reviews#edit
reviews#show
reviews#update
reviews#destroy

Disponible como params[:movie_id]


:movie_id es la FK almacenada en reviews que apunta a movie
Disponible como params[:id]
:id es la PK de reviews

GSyC!

38!

reviews_controller.rb
def create
@movie = Movie.find(params[:movie_id])
# asigna @movie.id a @review.movie_id
@review = @movie.reviews.build(params[:review])
# Recuerda: @current_user tiene el valor asignado en el filtro de
#
set_current_user de ApplicationController
# asigna @current_user.id a @review.moviegoer_id
@current_user.reviews << @review
if @review.save
flash[:notice] = 'Review successfully created.'
redirect_to(movie_review_path(@movie.id, @review.id))
else
render :action => 'new'
end
end

http://pastebin.com/ngViCREC

GSyC!

39!

views/reviews/new.html.haml
= form_tag movie_reviews_path(@movie) do
= label :review, :potatoes, 'How many potatoes?'
= select :review, :potatoes, [1,2,3,4,5]
= label :review, :comments, 'Comments'
= text_area :review, :comments
= submit_tag 'Create Review'

GSyC!

40!

Referencias!!
A Guide to Active Record Associations!
http://guides.rubyonrails.org/association_basics.html!

The Rails 3 Way, 2nd ed. Obie Fernndez.


Ed. Addison Wesley 2011. !
Database Management Systems, 3rd edition.
Raghu Ramakrishnan, Johannes Gehrke.
Ed. Mc Graw Hill, 2003. Captulos 1,2,3,5.!
Learning SQL. Alan Beaulieu. Ed. OReilly.
2005.!
41!

Potrebbero piacerti anche