github.com/Accefy/pop@v0.0.0-20230428174248-e9f677eab5b9/preload_associations_test.go (about) 1 package pop 2 3 import ( 4 "github.com/gobuffalo/nulls" 5 "github.com/stretchr/testify/require" 6 "testing" 7 ) 8 9 func Test_New_Implementation_For_Nplus1(t *testing.T) { 10 if PDB == nil { 11 t.Skip("skipping integration tests") 12 } 13 transaction(func(tx *Connection) { 14 a := require.New(t) 15 for _, name := range []string{"Mark", "Joe", "Jane"} { 16 user := User{Name: nulls.NewString(name)} 17 a.NoError(tx.Create(&user)) 18 19 book := Book{UserID: nulls.NewInt(user.ID)} 20 a.NoError(tx.Create(&book)) 21 22 writer := Writer{Name: "Larry", BookID: book.ID} 23 a.NoError(tx.Create(&writer)) 24 25 if name == "Mark" { 26 song := Song{UserID: user.ID} 27 a.NoError(tx.Create(&song)) 28 29 address := Address{Street: "Pop"} 30 a.NoError(tx.Create(&address)) 31 32 home := UsersAddress{UserID: user.ID, AddressID: address.ID} 33 a.NoError(tx.Create(&home)) 34 } 35 } 36 37 users := []User{} 38 a.NoError(tx.All(&users)) 39 40 // FILL THE HAS-MANY and HAS_ONE 41 a.NoError(preload(tx, &users)) 42 43 a.Len(users[0].Books, 1) 44 a.Len(users[1].Books, 1) 45 a.Len(users[2].Books, 1) 46 a.Equal(users[0].FavoriteSong.UserID, users[0].ID) 47 a.Len(users[0].Houses, 1) 48 49 book := Book{} 50 a.NoError(tx.First(&book)) 51 a.NoError(preload(tx, &book)) 52 a.Len(book.Writers, 1) 53 a.Equal("Larry", book.Writers[0].Name) 54 a.Equal("Mark", book.User.Name.String) 55 56 usersWithPointers := []UserPointerAssocs{} 57 a.NoError(tx.All(&usersWithPointers)) 58 59 // FILL THE HAS-MANY and HAS_ONE 60 a.NoError(preload(tx, &usersWithPointers)) 61 62 a.Len(usersWithPointers[0].Books, 1) 63 a.Len(usersWithPointers[1].Books, 1) 64 a.Len(usersWithPointers[2].Books, 1) 65 a.Equal(usersWithPointers[0].FavoriteSong.UserID, users[0].ID) 66 a.Len(usersWithPointers[0].Houses, 1) 67 }) 68 } 69 70 func Test_New_Implementation_For_Nplus1_With_UUID(t *testing.T) { 71 if PDB == nil { 72 t.Skip("skipping integration tests") 73 } 74 transaction(func(tx *Connection) { 75 a := require.New(t) 76 77 courses := []Course{} 78 for i := 0; i < 3; i++ { 79 course := Course{} 80 a.NoError(tx.Create(&course)) 81 courses = append(courses, course) 82 if i == 0 { 83 a.NoError(tx.Create(&CourseCode{ 84 CourseID: course.ID, 85 })) 86 } 87 } 88 89 courseCodes := []CourseCode{} 90 a.NoError(tx.All(&courseCodes)) 91 a.Len(courseCodes, 1) 92 93 // FILL THE HAS-MANY and HAS_ONE 94 a.NoError(preload(tx, &courseCodes)) 95 a.Equal(courses[0].ID, courseCodes[0].Course.ID) 96 97 student := Student{} 98 a.NoError(tx.Create(&student)) 99 100 parent := Parent{} 101 a.NoError(tx.Create(&parent)) 102 103 a.NoError(tx.RawQuery("insert into parents_students(parent_id, student_id) values(?,?)", parent.ID.String(), student.ID.String()).Exec()) 104 105 parents := []Parent{} 106 a.NoError(tx.All(&parents)) 107 108 a.NoError(preload(tx, &parents)) 109 a.Len(parents, 1) 110 a.Len(parents[0].Students, 1) 111 a.Equal(student.ID, parents[0].Students[0].ID) 112 }) 113 } 114 115 func Test_New_Implementation_For_Nplus1_With_NullUUIDs_And_FK_ID(t *testing.T) { 116 if PDB == nil { 117 t.Skip("skipping integration tests") 118 } 119 120 // This test suite prevents regressions of an obscure bug in the preload code which caused 121 // pointer values to be set with their empty values when relations did not exist. 122 // 123 // See also: https://github.com/gobuffalo/pop/issues/139 124 transaction(func(tx *Connection) { 125 a := require.New(t) 126 127 var server Server 128 a.NoError(tx.Create(&server)) 129 130 class := &NetClient{ 131 // The bug only appears when we have two elements in the slice where 132 // one has a relation and the other one has no such relation. 133 Hops: []Hop{ 134 {Server: &server}, 135 {}, 136 }} 137 138 // This code basically just sets up 139 a.NoError(tx.Eager().Create(class)) 140 141 var expected NetClient 142 a.NoError(tx.EagerPreload("Hops.Server").First(&expected)) 143 144 // What would happen before the patch resolved this issue is that: 145 // 146 // Classes.CourseCodes[0].Course would be the correct value (a filled struct) 147 // 148 // "server": { 149 // "id": "fa51f71f-e884-4641-8005-923258b814f9", 150 // "created_at": "2021-12-09T23:20:10.208019+01:00", 151 // "updated_at": "2021-12-09T23:20:10.208019+01:00" 152 // }, 153 // 154 // Classes.CourseCodes[1].Course would an "empty" struct of Course even though there is no relation set up: 155 // 156 // "server": { 157 // "id": "00000000-0000-0000-0000-000000000000", 158 // "created_at": "0001-01-01T00:00:00Z", 159 // "updated_at": "0001-01-01T00:00:00Z" 160 // }, 161 var foundValid, foundEmpty int 162 for _, hop := range expected.Hops { 163 if hop.ServerID.Valid { 164 foundValid++ 165 a.NotNil(hop.Server, "%+v", hop) 166 } else { 167 foundEmpty++ 168 a.Nil(hop.Server, "%+v", hop) 169 } 170 } 171 172 a.Equal(1, foundValid) 173 a.Equal(1, foundEmpty) 174 }) 175 } 176 177 func Test_New_Implementation_For_Nplus1_Single(t *testing.T) { 178 if PDB == nil { 179 t.Skip("skipping integration tests") 180 } 181 transaction(func(tx *Connection) { 182 a := require.New(t) 183 for _, name := range []string{"Mark", "Joe", "Jane"} { 184 user := User{Name: nulls.NewString(name)} 185 a.NoError(tx.Create(&user)) 186 187 book := Book{UserID: nulls.NewInt(user.ID)} 188 a.NoError(tx.Create(&book)) 189 190 writer := Writer{Name: "Larry", BookID: book.ID} 191 a.NoError(tx.Create(&writer)) 192 193 if name == "Mark" { 194 song := Song{UserID: user.ID} 195 a.NoError(tx.Create(&song)) 196 197 address := Address{Street: "Pop"} 198 a.NoError(tx.Create(&address)) 199 200 home := UsersAddress{UserID: user.ID, AddressID: address.ID} 201 a.NoError(tx.Create(&home)) 202 } 203 } 204 205 users := []User{} 206 a.NoError(tx.All(&users)) 207 208 // FILL THE HAS-MANY and HAS_ONE 209 a.NoError(preload(tx, &users, "Books")) 210 211 a.Len(users[0].Books, 1) 212 a.Len(users[1].Books, 1) 213 a.Len(users[2].Books, 1) 214 a.Zero(users[0].FavoriteSong.UserID) 215 a.Len(users[0].Houses, 0) 216 }) 217 } 218 219 func Test_New_Implementation_For_Nplus1_Nested(t *testing.T) { 220 if PDB == nil { 221 t.Skip("skipping integration tests") 222 } 223 transaction(func(tx *Connection) { 224 a := require.New(t) 225 var song Song 226 for _, name := range []string{"Mark", "Joe", "Jane"} { 227 user := User{Name: nulls.NewString(name)} 228 a.NoError(tx.Create(&user)) 229 230 book := Book{UserID: nulls.NewInt(user.ID)} 231 a.NoError(tx.Create(&book)) 232 233 if name == "Mark" { 234 song = Song{UserID: user.ID} 235 a.NoError(tx.Create(&song)) 236 237 address := Address{Street: "Pop"} 238 a.NoError(tx.Create(&address)) 239 240 home := UsersAddress{UserID: user.ID, AddressID: address.ID} 241 a.NoError(tx.Create(&home)) 242 } 243 } 244 245 SetEagerMode(EagerPreload) 246 users := []User{} 247 a.NoError(tx.Eager("Houses", "Books", "Books.User.FavoriteSong").All(&users)) 248 a.Len(users[0].Books, 1) 249 a.Len(users[1].Books, 1) 250 a.Len(users[2].Books, 1) 251 a.Len(users[0].Houses, 1) 252 253 a.Equal(users[0].ID, users[0].Books[0].User.ID) 254 a.Equal(song.ID, users[0].Books[0].User.FavoriteSong.ID) 255 SetEagerMode(EagerDefault) 256 }) 257 } 258 259 func Test_New_Implementation_For_Nplus1_BelongsTo_Not_Underscore(t *testing.T) { 260 if PDB == nil { 261 t.Skip("skipping integration tests") 262 } 263 transaction(func(tx *Connection) { 264 a := require.New(t) 265 user := User{Name: nulls.NewString("Mark")} 266 a.NoError(tx.Create(&user)) 267 268 taxi := Taxi{UserID: nulls.NewInt(user.ID)} 269 a.NoError(tx.Create(&taxi)) 270 271 SetEagerMode(EagerPreload) 272 taxis := []Taxi{} 273 a.NoError(tx.EagerPreload().All(&taxis)) 274 a.Len(taxis, 1) 275 a.Equal("Mark", taxis[0].Driver.Name.String) 276 SetEagerMode(EagerDefault) 277 }) 278 } 279 280 func Test_New_Implementation_For_BelongsTo_Multiple_Fields(t *testing.T) { 281 if PDB == nil { 282 t.Skip("skipping integration tests") 283 } 284 transaction(func(tx *Connection) { 285 a := require.New(t) 286 user := User{Name: nulls.NewString("Mark")} 287 a.NoError(tx.Create(&user)) 288 289 address := Address{HouseNumber: 2, Street: "Street One"} 290 a.NoError(tx.Create(&address)) 291 292 taxi := Taxi{UserID: nulls.NewInt(user.ID), AddressID: nulls.NewInt(address.ID)} 293 a.NoError(tx.Create(&taxi)) 294 295 book := Book{TaxiID: nulls.NewInt(taxi.ID), Title: "My Book"} 296 a.NoError(tx.Create(&book)) 297 298 SetEagerMode(EagerPreload) 299 books := []Book{} 300 a.NoError(tx.EagerPreload("Taxi.Driver", "Taxi.Address").All(&books)) 301 a.Len(books, 1) 302 a.Equal(user.Name.String, books[0].Taxi.Driver.Name.String) 303 a.Equal(address.Street, books[0].Taxi.Address.Street) 304 SetEagerMode(EagerDefault) 305 }) 306 } 307 308 func Test_New_Implementation_For_BelongsTo_Ptr_Field(t *testing.T) { 309 if PDB == nil { 310 t.Skip("skipping integration tests") 311 } 312 transaction(func(tx *Connection) { 313 a := require.New(t) 314 toAddress := Address{HouseNumber: 1, Street: "Destination Ave"} 315 a.NoError(tx.Create(&toAddress)) 316 317 taxi := Taxi{ToAddressID: &toAddress.ID} 318 a.NoError(tx.Create(&taxi)) 319 320 book1 := Book{TaxiID: nulls.NewInt(taxi.ID), Title: "My Book"} 321 a.NoError(tx.Create(&book1)) 322 323 taxiNilToAddress := Taxi{ToAddressID: nil} 324 a.NoError(tx.Create(&taxiNilToAddress)) 325 326 book2 := Book{TaxiID: nulls.NewInt(taxiNilToAddress.ID), Title: "Another Book"} 327 a.NoError(tx.Create(&book2)) 328 329 SetEagerMode(EagerPreload) 330 books := []Book{} 331 a.NoError(tx.EagerPreload("Taxi.ToAddress").Order("created_at").All(&books)) 332 a.Len(books, 2) 333 a.Equal(toAddress.Street, books[0].Taxi.ToAddress.Street) 334 a.NotNil(books[0].Taxi.ToAddressID) 335 a.Nil(books[1].Taxi.ToAddress) 336 a.Nil(books[1].Taxi.ToAddressID) 337 SetEagerMode(EagerDefault) 338 }) 339 } 340 341 func Test_New_Implementation_For_HasMany_Ptr_Field(t *testing.T) { 342 if PDB == nil { 343 t.Skip("skipping integration tests") 344 } 345 transaction(func(tx *Connection) { 346 a := require.New(t) 347 toAddress1 := Address{HouseNumber: 1, Street: "Destination Ave"} 348 a.NoError(tx.Create(&toAddress1)) 349 taxi1 := Taxi{Model: "Ford", ToAddressID: &toAddress1.ID} 350 a.NoError(tx.Create(&taxi1)) 351 taxi2 := Taxi{Model: "Honda", ToAddressID: &toAddress1.ID} 352 a.NoError(tx.Create(&taxi2)) 353 354 taxiNilToAddress := Taxi{ToAddressID: nil} 355 a.NoError(tx.Create(&taxiNilToAddress)) 356 357 toAddress2 := Address{HouseNumber: 2, Street: "Final Way"} 358 a.NoError(tx.Create(&toAddress2)) 359 taxi3 := Taxi{Model: "Mazda", ToAddressID: &toAddress2.ID} 360 a.NoError(tx.Create(&taxi3)) 361 362 SetEagerMode(EagerPreload) 363 addresses := []Address{} 364 a.NoError(tx.EagerPreload("TaxisToHere").Order("created_at").All(&addresses)) 365 a.Len(addresses, 2) 366 a.NotNil(addresses[0].TaxisToHere) 367 a.Len(addresses[0].TaxisToHere, 2) 368 a.Equal(taxi1.Model, addresses[0].TaxisToHere[0].Model) 369 a.Equal(taxi2.Model, addresses[0].TaxisToHere[1].Model) 370 a.NotNil(addresses[1].TaxisToHere) 371 a.Len(addresses[1].TaxisToHere, 1) 372 a.Equal(taxi3.Model, addresses[1].TaxisToHere[0].Model) 373 SetEagerMode(EagerDefault) 374 }) 375 }