Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help needed with queriesOne #613

Open
osteel opened this issue Jun 23, 2021 · 6 comments
Open

Help needed with queriesOne #613

osteel opened this issue Jun 23, 2021 · 6 comments
Labels

Comments

@osteel
Copy link
Contributor

osteel commented Jun 23, 2021

Hi,

I'm trying to use queriesOne for a resource but can't seem to get it right. To cut to the chase, this is the error I'm currently getting:

No JSON API resource type registered for PHP class Illuminate\Database\Eloquent\Builder

Now more about the setup: I've got some time entries which are periods of time recorded by workers (aka "punches"). These time entries have a worker ID as well as a task ID.

Now these workers can have different hourly rates and the applied one can differ based on the nature of the task. So I've got another table task_workers connecting the workers to tasks, and that table also has a rate ID.

This is roughly what it looks like:

Workers
    id

Tasks
    id

TimeEntries
    worker_id
    task_id

TaskWorkers
    worker_id
    task_id
    rate_id

Rates
    id

What I'm trying to achieve here, is the possibility to include the rate when querying the time entries, as a has-one relationship.

As you can see, using a regular Eloquent relationship is tricky (impossible, I think), because there is no direct link between time entries and task workers.

The query to fetch the rate from the TimeEntry model class is fairly straightforward, however:

    /**
     * The model's rate (query builder).
     *
     * @return Builder
     */
    public function rateQuery(): Builder
    {
        return Rate::query()
            ->join('task_workers', function (JoinClause $join) {
                $join->on('rates.id', '=', 'task_workers.rate_id')
                    ->where([
                        'task_workers.task_id'   => $this->task_id,
                        'task_workers.worker_id' => $this->worker_id,
                    ]);
            });
    }

This is the query I'm currently trying to use with the queriesOne relationship. And this is the corresponding method from the time entry's adapter:

    /**
     * Return the QueriesOne relationship for the rate.
     *
     * @return QueriesOne
     */
    protected function rate(): QueriesOne
    {
        return $this->queriesOne(function (TimeEntry $entry) {
            return $entry->rateQuery();
        });
    }

I also have the following value for $includePaths at the top, to avoid eager loading errors:

protected $includePaths = ['rate' => null];

I also added rate to the list of authorised includes in the validators file.

And I get the error mentioned at the beginning when I try querying the endpoint:

No JSON API resource type registered for PHP class Illuminate\Database\Eloquent\Builder

My guess is I'm using queriesOne in a wrong way, but I can't quite put my finger on it. Can you see what's wrong?

Cheers,

Yannick

@lindyhopchris
Copy link
Member

Hi! I can't immediately see a problem with the code. Are you able to share the stack trace for the error? Just wondering exactly where that is coming from.

@osteel
Copy link
Contributor Author

osteel commented Jun 23, 2021

@lindyhopchris thanks for the quick response. I'm still experimenting with this and it looks like removing the DATA bit on the relationship (from the time entry resource's schema) kinda fixed the issue, although I'm not 100% clear on what the consequences of doing this are.

I had this initially:

                return [
                    'rate' => [
                        self::SHOW_SELF    => false,
                        self::SHOW_RELATED => false,
                        self::SHOW_DATA    => isset($includedRelationships['rate']),
                        self::DATA         => function () use ($resource) {
                            return $resource->rateQuery();
                        },
                    ],
                ];

And now this:

                return [
                    'rate' => [
                        self::SHOW_SELF    => false,
                        self::SHOW_RELATED => false,
                        self::SHOW_DATA    => isset($includedRelationships['rate']),
                    ],
                ];

I've got a few other things to try – when I'm through with them I will give you an update, because overall I don't think I understand queriesOne and queriesMany well

@osteel
Copy link
Contributor Author

osteel commented Jun 23, 2021

I think I've figured it out. The confusion came from the fact that I can usually use the same method in both the adapter and the schema, e.g.:

// Adapter
/**
 * Return the BelongsTo relationship for the company.
 *
 * @return BelongsTo
 */
protected function company(): BelongsTo
{
    return $this->belongsTo('company');
}

// Schema
return [
    'rate' => [
        self::SHOW_SELF    => false,
        self::SHOW_RELATED => false,
        self::SHOW_DATA    => isset($includedRelationships['company']),
        self::DATA         => function () use ($resource) {
            return $resource->company;
        },
    ],
];

In the above company is a regular Eloquent relationship that also resolves to the model if called as an attribute (schema) rather than a function (adapter).

In my case the rateQuery method always returns a query Builder, so that can't work for the schema. Changing the code to this fixed it though:

return [
    'rate' => [
        self::SHOW_SELF    => false,
        self::SHOW_RELATED => false,
        self::SHOW_DATA    => isset($includedRelationships['rate']),
        self::DATA         => function () use ($resource) {
            return $resource->rateQuery()->first();
        },
    ],
];

I think the documentation could make it clearer here that when using queriesOne or queriesMany, the developer should make sure to return the query result and not the query builder (seems kinda obvious to me now, but mentioning it there could avoid some confusion I reckon 🙂 ).

@lindyhopchris
Copy link
Member

lindyhopchris commented Jun 23, 2021

Aha, you posted the solution just as I was writing a message telling you what the bug was!

Yeah, definitely could be clearer in the docs. The queriesOne() method returns an Eloquent Builder instance so the relationship can be queried as a relationship end-point... however when serializing the relationship in the schema you need to return the actual instance... which is what you figured out.

@lindyhopchris
Copy link
Member

Have labelled as a docs issue to remind me to update the docs!

@osteel
Copy link
Contributor Author

osteel commented Jun 23, 2021

Awesome! Thanks for your help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants