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