Call Query callback chain when preloading many2many (#1622)

When using `Preload` on a `many2many` association, the
`Query` callback chain was not being called. This made
it difficult to write a plugin that could reliably get
called regardless of how objects were being queried.

Now `handleManyToManyPreload` will call the `Query`
callback chain for each object that is retrieved by
following the association.

Since the data has already been read by the
`handleManyToManyPreload` method, a new scope setting
called `gorm:skip_queryCallback` is set to `true` before
calling the callbacks. Callbacks can check for the
presence of this setting if they should not be run; the
default `queryCallback` is an example of this case.

Fixes jinzhu/gorm#1621.
This commit is contained in:
Geoff Baskwill 2018-02-09 10:22:53 -05:00 committed by Jinzhu
parent 2bb1b7c83e
commit ec72a4cb6b
3 changed files with 48 additions and 0 deletions

View File

@ -15,6 +15,10 @@ func init() {
// queryCallback used to query data from database
func queryCallback(scope *Scope) {
if _, skip := scope.Get("gorm:skip_query_callback"); skip {
return
}
defer scope.trace(NowFunc())
var (

View File

@ -324,6 +324,10 @@ func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface
scope.scan(rows, columns, append(fields, joinTableFields...))
scope.New(elem.Addr().Interface()).
Set("gorm:skip_query_callback", true).
callCallbacks(scope.db.parent.callbacks.queries)
var foreignKeys = make([]interface{}, len(sourceKeys))
// generate hashed forkey keys in join table
for idx, joinTableField := range joinTableFields {

View File

@ -1627,6 +1627,46 @@ func TestPrefixedPreloadDuplication(t *testing.T) {
}
}
func TestPreloadManyToManyCallbacks(t *testing.T) {
type (
Level2 struct {
ID uint
}
Level1 struct {
ID uint
Level2s []Level2 `gorm:"many2many:level1_level2s;AssociationForeignKey:ID;ForeignKey:ID"`
}
)
DB.DropTableIfExists("level1_level2s")
DB.DropTableIfExists(new(Level1))
DB.DropTableIfExists(new(Level2))
if err := DB.AutoMigrate(new(Level1), new(Level2)).Error; err != nil {
t.Error(err)
}
lvl := Level1{
Level2s: []Level2{
Level2{},
},
}
DB.Save(&lvl)
called := 0
DB.Callback().Query().After("gorm:query").Register("TestPreloadManyToManyCallbacks", func(scope *gorm.Scope) {
called = called + 1
})
found := Level1{ID: lvl.ID}
DB.Preload("Level2s").First(&found, &found)
if called != 2 {
t.Errorf("Wanted callback to be called 2 times but got %d", called)
}
}
func toJSONString(v interface{}) []byte {
r, _ := json.MarshalIndent(v, "", " ")
return r