Eloquent - How to get model records where the count of that model for a parent model matches a column of that parent model instead of a PHP value? Pseudo: select * from answers where the count of answers on the answer's question is equal to the question's max_answers attribute?
Hello everyone,
I've been scratching my head at this one for a while and can't seem to come up with a decent solution. I'm hoping an Eloquent wizard can help with this, albeit edge case, challenge. I have attempted to do some Google + StackOverflow + Laracasts searching, but wasn't able to find something that deals with the specifics of this problem. I even bought & watched Jonathan Reinink's course on Eloquent to see if something might relate. Although there's a ton of useful info in there, I don't believe he provided an example like this.
The setup
I'm working with 2 Models as follows:
class Answer extends Model
{
// Just to show important attributes on the model...
protected $casts = [
'id' => 'int',
'question_id' => 'int',
'user_id' => 'int',
...
];
public function question(): BelongsTo
{
return $this->belongsTo(Question::class);
}
}
class Question extends Model
{
// Just to show important attributes on the model...
protected $casts = [
'id' => 'int',
'max_answers' => 'int',
...
];
public function answers(): HasMany
{
return $this->hasMany(Answers::class);
}
}
A straightforward $question->answers() arrangement, with one important caveat:
-
A question has a
max_answers attribute which, as you can imagine, determines the maximum number of answers that question is allowed to have.
- This
max_answers attribute is defined by the users of our system. They pay per answer provided to their question, so our users want to specify the maximum # of answers per question to control their budget.
- In other words, the
max_answers spec can't be hard-coded.
What I'm trying to achieve:
Get all of the answers that belong to questions where the count of answers on the related question is equal to the question's max_answers attribute.
- We could call these questions that cannot have any more answers (because they already have a count of answers that matches their
max_answers attribute) a "full" question for the sake of discussion.
Why (I don't believe) this is trivially simple
For "normal" situations involving counts of related models and constraining queries by them, one can do something like:
Answer::whereHas('question', fn ($query) => $query->has('answers', 5));
However, that makes the "count constraint" hard-coded (or a reference to something in PHP-land). That won't work for our requirements.
My closest attempt
// Answer Model
public function scopeBelongingToFullQuestion(Builder $query): Builder
{
return $query->whereHas('question', fn(Builder $question) => $question
->withCount('answers')
->havingRaw('answers_count >= max_answers')
);
}
This seems to produce the desired results with MySQL, but when running tests via SQLite, it throws an SQL exception stating it needs a GROUP BY to go along with the HAVING. Bugga. If I proceed to add a silly ->groupBy('id') into the query builder, I don't get any results in my tests.
Ok. That's the jist. If anyone can help clarify a good Eloquent statement that will produce the desired results in MySQL & SQLite, I'd be incredibly thankful.