Return empty slice for associations with 0 results

When using `Preload` to include the results of a "has many"
relationship, Gorm previously returned an uninitialized slice for any
such relations that bore zero records. This distinction was most
apparent when the results were marshalled to a JSON representation--a
record with zero related records would be represented with `null`.

For example, consider the following schema:

    id | name
    ---|------
    1  | Lorin
    2  | Sue

    id | p_id | value
    ---|------|-------------------
    1  | 1    | lorin@example.com
    2  | 1    | lorin2@example.com

Querying with:

    db.Preload("Email").Find(&people)

And marshalling the resulting value of `people` to JSON would yield the
following string:

    [
      {
        "name": "Lorin",
        "email": [
          "lorin@example.com",
          "lorin2@example.com"
        ]
      },
      {
        "name": "Sue",
        "email": null
      }
    ]

Beyond being inconsistent, the value `null` in this response differs
semantically from the actual state of the database. The database
actually has zero related records for the second user, so a JSON value
of `[]` is appropriate.

Update the callback that processes "has many" relationships to
communicate empty query results with an empty slice.
This commit is contained in:
Mike Pennisi 2016-09-27 16:55:04 -04:00
parent 5ec2f6ceb6
commit f06d6412de
2 changed files with 14 additions and 3 deletions

View File

@ -186,9 +186,11 @@ func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{})
for j := 0; j < indirectScopeValue.Len(); j++ { for j := 0; j < indirectScopeValue.Len(); j++ {
object := indirect(indirectScopeValue.Index(j)) object := indirect(indirectScopeValue.Index(j))
objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames) objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames)
f := object.FieldByName(field.Name)
if results, ok := preloadMap[toString(objectRealValue)]; ok { if results, ok := preloadMap[toString(objectRealValue)]; ok {
f := object.FieldByName(field.Name)
f.Set(reflect.Append(f, results...)) f.Set(reflect.Append(f, results...))
} else {
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
} }
} }
} else { } else {

View File

@ -90,6 +90,8 @@ func TestPreload(t *testing.T) {
} }
} else if len(user.Emails) != 0 { } else if len(user.Emails) != 0 {
t.Errorf("should not preload any emails for other users when with condition") t.Errorf("should not preload any emails for other users when with condition")
} else if user.Emails == nil {
t.Errorf("should return an empty slice to indicate zero results")
} }
} }
} }
@ -592,8 +594,14 @@ func TestNestedPreload9(t *testing.T) {
}, },
Level2_1: Level2_1{ Level2_1: Level2_1{
Level1s: []Level1{ Level1s: []Level1{
{Value: "value3-3"}, {
{Value: "value4-4"}, Value: "value3-3",
Level0s: []Level0{},
},
{
Value: "value4-4",
Level0s: []Level0{},
},
}, },
}, },
} }
@ -657,6 +665,7 @@ func TestNestedPreload10(t *testing.T) {
}, },
{ {
Value: "bar 2", Value: "bar 2",
LevelA3s: []*LevelA3{},
}, },
} }
for _, levelA2 := range want { for _, levelA2 := range want {