hcexile
hcexile

Reputation: 446

Lighthouse/GraphQL - Getting `null` for Polymorphic Relation in GraphQL Schema for Soft Deletes

I have a platform that enables users to download sound clips. Producers of the sound clips can see their history of downloads, but they can also delete files they don't want available for download anymore. Each download event has a DB record with a polymorphic relationship to two different tables, depending on the sound clip type.

I'm trying to put together a GraphQL query that will return the download statistics within a selected date range, including download stats of soft deleted files. The Laravel query is returning the soft deleted records but the soft deleted records aren't available on the graphQL side.

Laravel Download Model (each download event creates a record here) - it could be of a BirdSound or a HumanSound record:

class Download extends BaseModel
{
    protected $types = [
        'bird' => BirdSound::class,
        'human' => HumanSound::class,
    ];

    /**
     * @return MorphTo|EloquentBuilder|QueryBuilder
     */
    public function downloadable() : MorphTo
    {
        return $this->morphTo();
    }
}

Laravel BirdSound Model (there is an equivalent HumanSound model). There is one record in this table for each file available for download:

class BirdSounds extends Sounds
{
    use SoftDeletes;

    /**
     * @return MorphMany|EloquentBuilder|QueryBuilder
     */
    public function Download(): MorphMany
    {
        return $this->morphMany(Download::class, 'downloadable');
    }
}

GraphQL Schema:

type Download {
    id: ID!
    name: String!
    email: String!
    downloadable: Downloadable @morphTo
}

interface Downloadable {
    id: ID!
    name: String
    freeDownloads: [Download!] @morphMany
}

type BirdSound implements Downloadable {
    id: ID!
    name: String
    duration: String
    user: User @belongsTo(relation: "user")
    filename: String
    freeDownloads: [Download] @morphMany 
}

type HumanSound implements Downloadable {
    id: ID!
    name: String
    snippet: Int
    user: User @belongsTo(relation: "user")
    artwork_id: Int
    freeDownloads: [Download] @morphMany
}

# Using DownloadCount type to handle the data returned by the laravel function that counts the downloads
type DownloadCount {
    downloadable_id: Int
    downloadable_type: String
    count: Int
    downloadable: MediaInfo
}

# Using MediaInfo instead of the actual `downloadable` object in order to include soft-deleted records, 
# which I can't get working with the polymorphic relationship for lighthouse
type MediaInfo {
    id: ID! # ID of the downloadable record
    name: String # name from the BirdSound or HumanSound
}

extend type Query {
    myTopDownloads(downloaded_at: DateRange)): [DownloadCount]  
        @field(resolver: "App\\GraphQL\\Queries\\FreeDownloads@topDownloads")
}

Laravel function that gets the data:

public function topDownloads($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
{
    return auth()->guard('graphql')->user()->freeDownloads()
                    ->selectRaw('id, downloadable_id, downloadable_type, count(downloadable_id) as count')
                    ->groupBy('downloadable_id')
                    ->orderBy('count', 'desc')
                    ->with(['downloadable' => function($query){
                        $query->withTrashed();
                    }])
                    ->limit($limit)
                    ->get();
}

The above Laravel query returns both the download info AND the related downloadable data, whether it's been soft deleted or not, and with the graphQL schema described above, I can access the downloadable object through the MediaInfo relation on the Download object. However, I can't figure out how to get the actual downloadable relation as defined on the models available in graphQL - the relation always shows null.

I've tried the following:

Changing the Media type:

type Media {
    id: Int
    downloadable_id: Int
    name: String
    downloadable_type: String
    count: Int
    downloadable: Downloadable @morphTo @softDeletes
}

Adding trashed: Trash to the query (which I assume is only single-dimensional, which is why it wouldn't work):

extend type Query {
    myTopDownloads(trashed: Trash, downloaded_at: DateRange)): [Download]  
        @field(resolver: "App\\GraphQL\\Queries\\FreeDownloads@topDownloads")
}

...I've tried multiple variations of the above examples, all of which result in a null for the downloadable object or I get an error.

Query example:

{
  myTopDownloads{
    downloadable_id
    downloadable_type
    count
    downloadable(trashed:WITH) {
      __typename
      id
      name
    }
  }
}

"debugMessage": "Use @trashed only for Model classes that use the SoftDeletes trait.",

The Laravel models all use the SoftDeletes trait, and I couldn't find documentation on adding a specific trait to the graphQL type (ex: in my BirdSound type).

I assume the issue may be with the fact that it's a polymorphic relationship, but this is my first project using GraphQL and I'm still trying to wrap my head around some of the details...Any insight into this would be great!

Upvotes: 0

Views: 1469

Answers (1)

Werner
Werner

Reputation: 31

According to lighthouse, in a polymorphic relationship, you should point to a union indicating which types it may return.

union Downloadable = HumanSound | BirdSound

https://lighthouse-php.com/master/eloquent/polymorphic-relationships.html#one-to-many

To query those types, use the notation to specify on a given type, which fields to return

{
  myTopDownloads{
    downloadable_id
    downloadable_type
    count
    downloadable(trashed:WITH) {
      __typename
      ... on BirdSound {
        id
        name
      }
      ... on HumanSound {
        id
        name
      }
    }
  }
}

https://graphql.org/learn/schema/#union-types

Don't forget you need to specify the relationships in the models, including the return type, and remove the implements.

Upvotes: 3

Related Questions