Help needed with queriesOne
osteel opened this issue · 6 comments
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
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.
@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
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 🙂 ).
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.
Have labelled as a docs issue to remind me to update the docs!
Awesome! Thanks for your help