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  }