Base de datos
Asociaciones y Relaciones
Las asociaciones son la forma de Pop de definir una relación entre dos objetos en la base de datos. En este capítulo, aprenderás como definir asociaciones usando tags de estructura; y cómo manipularlos con el modificador Eager
.
Ejempl
type User struct {
ID uuid.UUID
Email string
Password string
Books Books `has_many:"books" order_by:"title asc"`
FavoriteSong Song `has_one:"song" fk_id:"u_id"`
Houses Addresses `many_to_many:"users_addresses"`
}
type Book struct {
ID uuid.UUID
Title string
Isbn string
User User `belongs_to:"user"`
UserID uuid.UUID
}
type Song struct {
ID uuid.UUID
Title string
UserID uuid.UUID `db:"u_id"`
}
type Address struct {
ID uuid.UUID
Street string
HouseNumber int
}
type Books []Book
type Addresses []Address
Tags de estructura disponibles
Usando el ejemplo anterior, el código a continuación, es una lista de los tags de estructura disponiblesy como usarlos.
-
has_many
: Este tag es usado para discribir relaciones uno a muchos en la base de datos. En el ejemplo, El tipoUser
define una relación con una lista de tipoBooks
mediante el uso del taghas_many
, lo que significa que unUser
puede tener variosBooks
. Cuando consultamos ne la base de datos, Pop cargará todos los registros de la tablabooks
que tengan una columna llamadausers_id
, o la columna especificada confk_id
que coincida con el valor deUser.ID
. -
belongs_to
: Este tag se usa para describir el propietario en la relación. Un propietario representa una dependencia altamente acoplada entre el modelo y el campo de asociación de destino donde el tagbelongs_to
fue definido. Este tag se usa principalmente para indicar que el modelo posee su “existencia” en el campo de asociaciónbelongs_to
. En el ejemplo de arriba, el tipoBook
usabelongs_to
para indicar que su propietario es el tipoUser
. cuando consultamos en la base de datos, Pop cargará un registro de la tablausers
con unid
que coincida con el valor del campoBook.UserID
. -
has_one
: Este tag se usa para describir las relaciones uno a uno en la base de datos. En el ejemplo de arriba, hay unaFavoriteSong
dentro de todas los registros de canciones que el tipoUser
le gusta más. Cuando consultamos en la base de datos, Pop cargará un registro de la tablasongs
que tiene una columna llamadauser_id
, una columna especificada confk_id
que coincide con el valor del campoUser.ID
. -
many_to_many
: Este tag se usa para describir relaciones muchos a muchos en la base de datos. En el ejemplo de arriba, la relacion entreUser
y la lista de tipoAddresses
existe para indicar que unUSer
puede ser propietario de muchasHouses
y unaHouse
ser propiedad de muchosUsers
. Es importante notar que el valor para el tagmany_to_many
es la tabla que conecta ambos lados en la relación; en el ejemplo de arriba, este valor se define comousers_addresses
. Cuando consultamos en la base de datos, Pop cargará todos los registros de la tablaaddresses
mediante la tabla asociativausers_addresses
. La tablausers_addresses
DEBE tener definidas las columnasaddress_id
yuser_id
para coincidir con los valores de los camposAddress.ID
yUser.ID
. Tambien puedes definir un tagfk_id
que se usará en la asociacion de destino, es decir, la tablaaddresses
. -
fk_id
: Este tag se puede usar para definir el nombre de la columna en la asociación de destino que coincida con elID
del modelo. En el ejemplo de arriba,Song
tiene una columna llamadau_id
que hace referencia al id de la tablausers
. Cuando cargamosFavoriteSong
,u_id
será usado en lugar deuser_id
. -
order_by
: Este tag se puede usar en combinacion con los tagshas_many
ymany_to_many
para indicar el orden de la asociación cuando se cargan. La forma de uso es:order_by:"<column_name> <asc | desc>"
Cargando asociaciones
Pop actualmente proporciona dos modos para cargar asociaciones; cada modo afectará la forma en que Pop carga las asociaciones y consultas a la base de datos.
Eager. El modo por defecto. Al habilitar este modo, pop realizará “n” consultas para cada asociación definida en el modelo. Esto significa más llamadas a la base de datos para no afectar el uso de la memoria.
EagerPreload. Modo opcional. Al habilitar este modo, Pop ejecutará una consulta para cada asociación definida en el modelo. Este modo llamará a la base de datos con una frecuencia reducida sacrificando más espacio en memoria.
-
pop.SetEagerMode
: Pop permite habilitar alguno de estos modos globalmente, lo que afectará el rendimiento del manejo de TODAS las consultas. UsaEagerDefault
oEagerPreload
como parametro para activar alguno de estos modos. -
tx.EagerPreload | q.EagerPreload
: Pop permite a los desarrolladores controlar en cuales situaciones quienen que Pop ejecute cualquiera de estos modos cuando sea necesario. Este método activará el modoEagerPreload
solo para la consulta en acción. -
tx.Eager | q.Eager
: Pop permite a los desarrolladores controlar en cuales situaciones quienen que Pop ejecute cualquiera de estos modos cuando sea necesario. Este metodo activará el modoEager
solo para la consulta en acción.
Modo Eager
El método pop.Connection.Eager()
le indica a Pop que cargue las asociaciones para un modelo una vez que ese modelo se carge desde la base de datos.
Este modo ejecutará “n” consultas para cada asociacion definida en el modelo.
for i := 0; i < 3; i++ {
user := User{ID: i + 1}
tx.Create(&user)
}
for i := 0; i < 3; i++ {
book := Book{UserID: i +1}
tx.Create(&book)
}
u := Users{}
// Carga todas las asociaciones para cada usuario registrado. Por ejemplo `Books`, `Houses` y `FavoriteSong`
err := tx.Eager().All(&u)
El modo Eager
va a:
- Cargar todos los users.
SELECT * FROM users;
- Recorrer cada usuario y cargar sus asociaciones
SELECT * FROM books WHERE user_id = 1
SELECT * FROM books WHERE user_id = 2
SELECT * FROM books WHERE user_id = 3
Modo EagerPreload
El método pop.Connection.EagerPreload()
le indica a pop que cargue las asociaciones para un modelo una vez que el modelo es cargado desde la base de datos. Este modo llamará a la base de datos con una frecuencia reducida sacrificando más espacio en memoria.
for i := 0; i < 3; i++ {
user := User{ID: i + 1}
tx.Create(&user)
}
for i := 0; i < 3; i++ {
book := Book{UserID: i +1}
tx.Create(&book)
}
u := Users{}
// Carga todas las asociaciones para cada usuario registrado. Por ejemplo `Books`, `Houses` y `FavoriteSong`
err := tx.EagerPreload().All(&u)
El modo EagerPreload
va a:
- Cargará todos los usuarios.
SELECT * FROM users;
- Carga las asociaciones para todos los usuarios en una sola consulta.
SELECT * FROM books WHERE user_id IN (1, 2, 3)
Cargar asociaciones específicas
Por defecto, Eager
y EagerPreload
cargarán todas las asociaciones asignadas para el modelo. PAra especificar cuales asociaciones deben ser cargadas, puedes pasar los nombres de esos campos a los metodos Eager
o EagerPreload
y solo se cargarán esas asociaciones.
// Cargar solo las asociaciones `Books` para los usuarios con nombre `Mark`
u := []User{}
// Usando modo `Eager`
err := tx.Eager("Books").Where("name = 'Mark'").All(&u)
// Usando modo `EagerPreload`
err := tx.EagerPreload("Books").Where("name = 'Mark'").All(&u)
Pop también te permite cargar asociaciones anidadas usando el caracter .
para concatenarlos. Echale un vistazo al siguiente ejemplo.
// Cargará todos los `Books` para `u` y para cada `Book`, cargará el usuario que será el mismo que `u`
u := User{}
// Usando modo `Eager`
tx.Eager("Books.User").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.User").First(&u)
// Cargará todos los `Books` para `u` y para cada `Book` cargará todos los `Writers` y para cada `Writer` cargará la asociación `Book`.
u := User{}
// Usando modo `Eager`
tx.Eager("Books.Writers.Book").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.Writers.Book").First(&u)
// Cargará todos los `Books` para `u` y para cada `Book` cargará todos los `Writers`. Y también cargará `FavoriteSong` de `u`.
u := User{}
// Usando modo `Eager`
tx.Eager("Books.Writers").Eager("FavoriteSong").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.Writers").EagerPreload("FavoriteSong").First(&u)
Cargar asociaciones para un modelo existente
El método pop.Connection.Load()
toma una estructura de modelo, que ha sido llenado desde la base de datos, y una lista opcional de asociaciones para cargar.
u := User{}
// Carga todas las asociaciones para `u`. Por ejemplo `Books`, `Houses` y `FavoriteSong`
tx.Load(&u)
// Carga solo las asociaciones `Books` para `u`
tx.Load(&u, "Books")
El método Load
no recuperará el User
de la base de datos, solo sus asociaciones.
Creación anidada plana
Pop te permite crear los modelos y sus asociaciones con otros modelos en un solo paso de manera predeterminada. Ya no necesitas crear cada asociación por separado. Pop incluso creará registros de las tablas asociativas para las asociaciones many_to_many
.
Suponiendo las siguientes piezas de pseudocódigo:
book := Book{Title: "Pop Book", Description: "Pop Book", Isbn: "PB1"}
tx.Create(&book)
song := Song{Title: "Don't know the title"}
tx.Create(&song)
addr := Address{HouseNumber: 1, Street: "Golang"}
tx.Create(&addr)
user := User{
Name: "Mark Bates",
Books: Books{Book{ID: book.ID}},
FavoriteSong: song,
Houses: Addresses{
addr,
},
}
err := tx.Create(&user)
-
Notarás que
Books
es una asociacionhas_many
y se dará cuenta que para actualizar realmente cadaBook
, primero necesitará tener elUser ID
. Por lo tanto, procede a guardar los datos deUser
para que pueda recuperar el ID y lueo usar ese ID para llenar el campoUserID
en todos losBooks
. Actualiza todos losBooks
en la base de datos usando susID
s para orientarlos. -
FavoriteSong
es una asociacionhas_one
y usa la misma logica descrita en la asociaciónhas_many
. Dado que los datos deUser
se guardaron previamente antes de actuializar todos losBooks
afectados, Se sabe que elUser
tiene unID
, por lo que llena su campoUserID
con ese valor yFavoriteSong
se actualiza en la base de datos. -
Houses
en este ejemplo es una relacionmany_to_many
y se tendrá que tratar con dos tablas en este caso:users
yaddresses
. Debido a queUser
ya estaba almacenado, ya se tiene suID
. A continación, se usarán losID
’s pasados con losAddresses
para crear las entradas correcpondientes en la tabla asociativa.
Para una asociación belongs_to
como se muestra en el siguiente ejemplo, llena su campo UserID
antes de guardarse en la base de datos.
book := Book{
Title: "Pop Book",
Description: "Pop Book",
Isbn: "PB1",
User: user,
}
tx.Create(&book)
Creacion con Eager
Pop tambien te permite crear modelos e integrar la creación de sus asociaciones en un solo paso
Suponiendo las siguientes piezas de pseudocódigo:
user := User{
Name: "Mark Bates",
Books: Books{{Title: "Pop Book", Description: "Pop Book", Isbn: "PB1"}},
FavoriteSong: Song{Title: "Don't know the title"},
Houses: Addresses{
Address{HouseNumber: 1, Street: "Golang"},
},
}
err := tx.Eager().Create(&user)
-
Notará que
Books
es una asociaciónhas_many
y se dará cuenta de que para almacenar cadaBook
, primero necesitará obtener elUserID
. Por lo tanto, procede primero a guardar/crear los datos deUser
para que pueda recuperar un ID y luego usar esa ID para rellenar el campoUserID
en cadaBook
enBooks
. Después guarda todos losBooks
en la base de datos. -
FavoriteSong
es una asociaciónhas_one
y usa la misma lógica descrita en la asociaciónhas_many
. Dado que los datos delUser
se guardaron previamente antes de crear todos losBooks
, se sabe que elUser
tiene unID
, por lo que llena su campoUserID
con ese valor y luegoFavoriteSong
se guarda en la base de datos. -
Houses
en este ejemplo es una relaciónmany_to_many
y tendrá que tratar con dos tablas, en este caso:users
yaddresses
. Primero deberá guardar todas lasAddresses
en la tablaaddresses
antes de guardarlas en la tabla asociativa. Debido a queUser
ya está guardado, ya tiene unID
.- Este es un caso especial a tratar, ya que este comportamiento es diferente de todas las demás asociaciones, se soluciona implementando la interfaz
AssociationCreatableStatement
, todas las demás asociaciones implementan por defecto la interfazAssociationCreatable
.
- Este es un caso especial a tratar, ya que este comportamiento es diferente de todas las demás asociaciones, se soluciona implementando la interfaz
Para una asociación belongs_to
como la que se muestra en el siguiente ejemplo, primero deberá crear User
para recuperar su valor ID y luego rellenar su campo UserID
antes de guardarlo en la base de datos.
book := Book{
Title: "Pop Book",
Description: "Pop Book",
Isbn: "PB1",
User: User{
Name: nulls.NewString("Larry"),
},
}
tx.Eager().Create(&book)
En caso de que alimentes la creación con Eager
con modelos asociados que ya existen, en lugar de crear duplicados o actualizar su contenido, simplemente creará/actualizará las asociaciones con ellos.