+ All Categories
Home > Documents > 11 Asociaciones en Rails

11 Asociaciones en Rails

Date post: 25-Jan-2016
Category:
Upload: luis-fernando-jimenez
View: 8 times
Download: 0 times
Share this document with a friend
Description:
todos los tipos de asociaciones en ruby on rails
Popular Tags:
41
Asociaciones en Rails Ingeniería de Sistemas de Información Grado en Ingeniería en Tecnologías de Telecomunicación GSyC
Transcript
Page 1: 11 Asociaciones en Rails

Asociaciones en Rails!

Ingeniería de Sistemas de Información!

Grado en Ingeniería en Tecnologías de Telecomunicación!

GSyC!

Page 2: 11 Asociaciones en Rails

• ©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 Attribution-

NonCommercial-ShareAlike 3.0 Unported License!

2!©GSyC!

Page 3: 11 Asociaciones en Rails

Asociaciones y Claves foráneas(ELLS §7.3)!

Page 4: 11 Asociaciones en Rails

Asociaciones en Rails!•  Sirven para modelar las relaciones lógicas entre dos

tipos de entidades en una arquitectura SW!•  ActiveRecord::Associations proporciona un conjunto de

métodos de clase que generan métodos proxy!•  Los métodos proxy generados ligan instancias de

modelos que están relacionadas entre sí a través de claves foráneas de sus respectivas tablas en la BD!

•  Hacen más natural y fácil la creación, modificación y eliminación de modelos relacionados/asociados a través de claves foráneas en las tablas de la BD sin tener que usar operadores relacionales!

4!©GSyC!

Page 5: 11 Asociaciones en Rails

Ejemplo: queremos añadir críticas/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

puntuación (# de potatoes) y un comentario!•  Cada moviegoer puede escribir 0 o más reviews!•  Cada movie podrá tener 0 o más 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!5!©GSyC!

Page 6: 11 Asociaciones en Rails

Diagrama Entidad/Relación!

•  Las entidades Moviegoer y Movie mantienen entre sí una relación, Reviews, que es muchos-a-muchos!– Un Moviegoer puede criticar 0 o muchas movies!– Una Movie puede ser criticada por 0 o muchos

moviegoers!

6!©GSyC!

uid provider

release_date

title

Movie Moviegoer

name

Reviews

potatoes rating

description

id id comments

Page 7: 11 Asociaciones en Rails

Modelo relacional. Diagrama ~UML!

•  Un Moviegoer tiene 0 o más reviews (*)!–  1..n significaría al menos una review!

•  Una Movie tiene 0 o más reviews (*)!–  1..n significaría al menos una review!

•  Una Review pertenece a un sólo Moviegoer (1)!

–  0..1 significaría que puede haber reviews que no pertenecen a ningún Moviegoer!

•  Una review pertenece a una sola Movie!–  0..1 significaría que puede haber reviews de

películas que no están en la BD!

Al pasar del modelo E/R al modelo relacional, en Rails utilizaremos un nuevo modelo/tabla Review para modelar la relación Reviews!

–  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!

7!©GSyC!

Page 8: 11 Asociaciones en Rails

Modelo Relacional!•  En el modelo relacional las relaciones entre tablas se expresan con claves foráneas

(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!

8!©GSyC!

id! title! rating!

13! Inception! PG-13!

41! Star Wars! PG!

43! Itʼs Complicated! R!

id! movie_id! moviegoer_id! potatoes!

21! 41! 1! 5!

22! 13! 2! 3!

23! 13! 1! 4!

id! name!

1! alice!

2! bob!

3! carol!

reviews

movies moviegoers

Page 9: 11 Asociaciones en Rails

Operaciones relacionales!

• Se pueden expresar y manipular relaciones complejas usando operaciones de álgebra relacional que operan sobre una colección de tablas!

• Ejemplo: todas las reviews de la película 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 (sólo 2 filas de las anteriores)

• Nos quedamos con las que tengan movies.id=13 (sólo una fila de las anteriores)

9!©GSyC!

Page 10: 11 Asociaciones en Rails

Operaciones relacionales!tabla ’movies' tabla 'reviews'

id title id potatoes movie_id

13 Inception 21 5 41

41 Star Wars 22 3 13

43 It’s complicated 23 4 13

Producto cartesiano: movies JOIN reviews

movies.id movies.title reviews.id reviews.potatoes reviews.movie_id

13 Inception 21 5 41

13 Inception 22 3 13

13 Inception 23 4 13

41 Star Wars 21 5 41

41 Star Wars 22 3 13

41 Star Wars 23 4 13

43 It’s complicated 21 5 41

43 It’s complicated 22 3 13

43 It’s complicated 23 4 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 4 13

41 Star Wars 21 5 41

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

movies.id movies.title reviews.id reviews.potatoes reviews.movie_id

13 Inception 23 4 13 10!©GSyC!

Page 11: 11 Asociaciones en Rails

Operaciones relacionales!• Se pueden expresar y manipular

relaciones complejas usando operaciones de álgebra relacional que operan sobre una colección de tablas!

• Ejemplo: todas las reviews de la película 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"

Producto cartesiano!

11!©GSyC!

Page 12: 11 Asociaciones en Rails

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 gustaría manejar objetos Ruby:!–  Una movie has_many reviews!–  Una review belongs_to una película!–  Un moviegoer has_many reviews!–  Una review belongs_to un moviegoer !

12!©GSyC!

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

alice_review = Review.new(:potatoes => 5, :comments => "Magnífica") 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']

http://pastebin.com/NcezVx7R

Page 13: 11 Asociaciones en Rails

Módulo ActiveRecord::Associations!

•  has_many, has_one, belongs_to son llamadas a métodos de ActiveRecord::Associations!

•  Estos métodos de clase utilizan metaprogramación para generar métodos 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 términos OO y no en términos del modelo relacional!

•  Asocian modelos, permitiendo manipular las relaciones del modelo relacional implementadas en las tablas con PK, FK, de un modo más 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 términos de PK, FK, de joins,… de relaciones/tablas!

–  ¡OJO!: el programador tiene que añadir las FK en la migración!

13!©GSyC!

Page 14: 11 Asociaciones en Rails

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

14!©GSyC!

Page 15: 11 Asociaciones en Rails

rails generate migration create_reviews Y luego editamos la migración:!

t.references 'movie' crea el campo FK movie_id t.references 'moviegoer' crea el campo FK moviegoer_id

Migración para añadir FK!

15!©GSyC!

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

Page 16: 11 Asociaciones en Rails

Asociaciones entre modelos!

16!©GSyC!

class Review < ActiveRecord::Base belongs_to :movie belongs_to :moviegoer end

http://pastebin.com/GBTCH47f Asociaciones entre modelos!

class Movie < ActiveRecord::Base has_many :reviews … end

class Moviegoer < ActiveRecord::Base has_many :reviews … end

has_one es como has_many, pero sólo puede poseer uno!

Page 17: 11 Asociaciones en Rails

Métodos proxy de la Association!

• Ahora ya podemos escribir código más OO para manipular la BD relacional:@movie.reviews # Enumerable

• Y en el otro sentido:@review.movie

• Se pueden añadir 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 => '...')

17!©GSyC!

Page 18: 11 Asociaciones en Rails

Métodos 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

Añade 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 película 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 18!©GSyC!

Page 19: 11 Asociaciones en Rails

¿Cómo funciona?!•  Los modelos que participan en una asociación 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 foráneas en la tabla del modelo que tiene belongs_to. !

–  Ejemplo: si en el modelo Review aparece belongs_to :movie entonces en la migración de la tabla reviews tiene que aparecer la FK como t.references movie

19!©GSyC!

Page 20: 11 Asociaciones en Rails

Resumen/receta: para añadir una asociación uno-a-muchos

(one-to-many) en RoR!1.  Añadir has_many al fichero del modelo que posee

y belongs_to en el lado del modelo poseído!2.  Crear la migración para añadir la FK al lado

poseído para que referencie al que le posee!3.  Aplicar la migración!4.  rake db:test:prepare para regenerar el

esquema de la BD de test!

20!©GSyC!

Page 21: 11 Asociaciones en Rails

Integridad referencial!•  ¿Qué ocurre si borramos una película de la que existen críticas?!

–  El campo FK movie_id de sus críticas en la tabla reviews referencia una PK que ya no existe. !

–  Esta es una razón por la que las PK nunca se reciclan en una tabla: sería desastroso si una nueva película adquiriese la misma clave primaria que la antigua película!

•  Podemos optar por alguna de estas alternativas:!–  Borrar las críticas de una peli cuando se borra esa peli has_many :reviews, :dependent => :destroy

–  Dejar las críticas huérfanas (sin dueño)! has_many :reviews, :dependent => :nullify

•  Se pueden hacer otras cosas, usando las callbacks del ciclo de vida del modelo explícitamente (ej: mezclando)!

•  A la hora de crear una review también podemos controlar la integridad referencial: se puede impedir que se creen reviews de películas que no existan en la tabla movies con una validación en el modelo Review: validate_presence_of :movie

21!©GSyC!

Page 22: 11 Asociaciones en Rails

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

end

lambda { review.find(review_id) }.should raise_error(ActiveRecord::RecordNotFound)

22!©GSyC!

Page 23: 11 Asociaciones en Rails

Asociaciones múltiples muchos-a-muchos / many-to-many

con has_many X :through Y y has_and_belongs_to_many X

Page 24: 11 Asociaciones en Rails

Muchos a muchos!

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

 ¿Cómo obtener todas las movies criticadas por un moviegoer? ! 24!©GSyC!

Page 25: 11 Asociaciones en Rails

Asociaciones muchos-a-muchos!

•  Escenario: Los moviegoers critican movies!– un moviegoer puede tener

muchas críticas!– pero una movie también puede tener muchas

críticas!• Movie tiene una relación muchos-a-

muchos indirecta, a través de Review, con Moviegoer!– Una Movie es criticada por muchos Moviegoers!– Un Moviegoer critica muchas Movies !

25!©GSyC!

Page 26: 11 Asociaciones en Rails

Asociaciones muchos-a-muchos!

• ¿Cómo 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

26!©GSyC!

Page 27: 11 Asociaciones en Rails

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

27!©GSyC!

Page 28: 11 Asociaciones en Rails

Through!•  Ahora podemos escribir esto:!

@user.movies # movies rated by user

@movie.moviegoers # users who rated this movie

28!©GSyC!

Page 29: 11 Asociaciones en Rails

Through: join de 3 tablas!•  En SQL, suponiendo que @user.id == 1, la

consulta @user.movies habría 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”

29!©GSyC!

Page 30: 11 Asociaciones en Rails

Through: OJO!• Nunca deberíamos hacer esto:!

alice = Moviegoer.find_by_name(‘Alice’)

alice.movies << Movie.find_by_name(‘Inception’)

Dado que movies es un has_many through reviews, crearía una review con potatoes y comments a nil (¿qué si no?) para ligar la moviegoer alicia con la película!

Podríamos evitarlo añadiendo validates_presence_of :comments y validates_presence_of :potatoes al modelo Review

30!©GSyC!

Page 31: 11 Asociaciones en Rails

Has_and_belongs_to_many!•  En una relación many-to-many en la que no hay atributos

descriptivos en la relación, ¿para qué queremos el modelo Review?!

•  Ejemplo: moviegoers likes movies!–  Queremos poder saber las películas que le gustan a un moviegoer, y

todos los moviegoers a los que les gusta una película!–  @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 relación, que ha de llamarse !

31!©GSyC!

Page 32: 11 Asociaciones en Rails

Has_and_belongs_to_many!•  El nombre de la tabla ha de ser la concatenación de los nombres de las

tablas de las entidades, en orden lexicográfico y separados por un underscore:!–  Movies y moviegoers => moviegoers_movies!

•  En la migración se incluyen los campos de las foreign keys, y se requiere explícitamente que no se añada campo de clave para esta tabla:!

•  Los modelos incluyen ambos has_and_belongs_to_many

32!©GSyC!

class CreateLikesJoinTable < ActiveRecord::Migration   def change     create_table :moviegoers_movies, :id => false do |t|       t.references :moviegoer       t.references :movie     end   end end

class Movie < ActiveRecord::Base   has_and_belongs_to_many :moviegoers end

class Moviegoer < ActiveRecord::Base   has_and_belongs_to_many :movies end

Page 33: 11 Asociaciones en Rails

El equivalente a las vistas::class_name, :source, :conditions, :order

Page 34: 11 Asociaciones en Rails

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

34!©GSyC!

Page 35: 11 Asociaciones en Rails

Cómo afectan las asociaciones a los

controladores y las vistas(ELLS §7.4)!

Page 36: 11 Asociaciones en Rails

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 botón para ir al formulario de crear review!

• Podríamos 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! 36!©GSyC!

Page 37: 11 Asociaciones en Rails

Rutas RESTful anidadas!• Podemos reflejar en las URLs la asociación

entre movies y reviews!• En config/routes.rb cambiamosresources :movies por resources :movies do resources :reviews end

• Es una ruta anidada (nested route)!• Significa que no puedes acceder a reviews sin

nombrar la película a la que se refiere la review!

37!©GSyC!

Page 38: 11 Asociaciones en Rails

Rutas RESTful anidadas!!  rake routes:!

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 38!©GSyC!

movie_reviews GET /movies/:movie_id/reviews(.:format) reviews#index POST /movies/:movie_id/reviews(.:format) reviews#create new_movie_review GET /movies/:movie_id/reviews/new(.:format) reviews#new edit_movie_review GET /movies/:movie_id/reviews/:id/edit(.:format) reviews#edit movie_review GET /movies/:movie_id/reviews/:id(.:format) reviews#show PUT /movies/:movie_id/reviews/:id(.:format) reviews#update DELETE /movies/:movie_id/reviews/:id(.:format) reviews#destroy

Page 39: 11 Asociaciones en Rails

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

39!©GSyC!

http://pastebin.com/ngViCREC

Page 40: 11 Asociaciones en Rails

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'

40!©GSyC!

Page 41: 11 Asociaciones en Rails

Referencias!!•  A Guide to Active Record Associations!

– http://guides.rubyonrails.org/association_basics.html!

•  The Rails 3 Way, 2nd ed. Obie Fernández. Ed. Addison Wesley 2011. !

• Database Management Systems, 3rd edition. Raghu Ramakrishnan, Johannes Gehrke. Ed. Mc Graw Hill, 2003. Capítulos 1,2,3,5.!

•  Learning SQL. Alan Beaulieu. Ed. OʼReilly. 2005.!

41!


Recommended