Laravel Model must return a relationship instance - laravel

My website has comments. These comments can have "votes", upvotes and downvotes.
I have a Comment Model and a CommentVote model.
In my comment model I have a functions that returns the votes:
public function votes() {
return $this->hasMany('App\CommentVote', 'comment_id');
}
public function upvotes() {
return $this->hasMany('App\CommentVote', 'comment_id')->where('vote', 1);
}
public function downvotes() {
return $this->hasMany('App\CommentVote', 'comment_id')->where('vote', -1);
}
Notice that upvotes are stored in the database in a tinyInt as 1 and downvotes are stored as -1
In my CommentVote model I have the belongsTo relationship:
public function comment() {
return $this->belongsTo('App\Comment');
}
Now I want to have a function that calculates the total "score" of the comment. Total upvotes minus total downvotes.
I try to make a function that counts all the upvotes - all the downvotes.
public function score() {
return $this->upvotes()->count() - $this->downvotes()->count();
}
This returns the error:
App\Comment::score must return a relationship instance.
In fact using the count() anywhere will return this error, despite it working fine in my other Models.
Doing something simple like:
public function voteCount() {
return $this->hasMany('App\CommentVote', 'comment_id')->count();
or even
return $this->votes()->count();
}
will return the error:
App\Comment::voteCount must return a relationship instance.
Why is this happening?
EDIT:
Here is the controller, as per requests in the comments:
public function getSubmission($subchan, $id, $URLtitle) {
$submission = Submission::where('id', $id)->first();
$comments = Comment::where('submission_id', $submission->id)->where('parent_id', NULL)->orderBy('created_at', 'desc')->get();
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
});
if (!$submission) {
return redirect()->route('home')->with('error', 'Submission not found.' );
}
return view('submissions.submission')
->with('submission', $submission)
->with('navSubchan', $submission->getSubchan->name)
->with('submissionPage', 1)
->with('comments', $comments)
;
}

I suspect you're doing $model->score, which is going to look for a function called score(), but in a specific manner that expects that function to return a HasMany, HasOne, BelongsTo etc. style relationship object.
Consider an accessor function instead.
public function getScoreAttribute() {
return $this->upvotes()->count() - $this->downvotes()->count();
}
allows you to do $model->score successfully.

Related

Laravel retrieve multiple relationships

I have a laravel project where a User can have many Client class. The Client can have many Session and and a Session can have many Assessment and many Plan. I am using hasManyThrough on the Client to get Assessment and Plan. Each Assessment and Plan has a review_date timestamp saved into the database.
What I'd like to do is get all the Assessment and Plan for any Client with their review_date as today. Ideally something like:
$events = Auth::user()->reviews()->today();
What I don't know how to do it make the reviews function, because it's essentially combining 2 relationships.
Can anyone help me out?
User.php
public function clients()
{
return $this->hasMany(Client::class);
}
public function assessments()
{
return $this->hasManyThrough(Assessment::class, Client::class);
}
public function plans()
{
return $this->hasManyThrough(Plan::class, Client::class);
}
public function reviews()
{
// return all assessments and plans
}
public function scopeToday(Builder $query)
{
$query->whereDate('review_date', Carbon::today());
}
Client.php
public function assessments()
{
return $this->hasManyThrough(Assessment::class, Session::class);
}
public function plans()
{
return $this->hasManyThrough(Plan::class, Session::class);
}
Session.php
public function assessments()
{
return $this->hasMany(Assessment::class);
}
public function plans()
{
return $this->hasMany(Plan::class);
}
You can get a collection from both methods, so you could simply merge the 2 together. (Be warned, this will lead to ugly code later when you have to check object types during the loop.) You can't chain the scope method, since you aren't getting back a relationship object, but you could pass the date as a parameter instead, or just fix it at today's date if you'll never need other dates.
public function reviews(Carbon $date)
{
return $this
->assessments()
->whereDate('review_date', $date)
->get()
->toBase()
->merge(
$this->plans()->whereDate('review_date', $date)->get()->toBase()
)
->sortBy('review_date');
}
And then call it like this:
$date = now();
$events = Auth::user()->reviews($date);

Getting extra fields from laravel api having belongstomany relationship

I have two data tables related to each other by the belongstomany relationship. And when I am fetching data from its api controllers with selecting only two column keys ['id','title'] yet it returns some extra data in the response object.
modelcode:
public function place(){
return $this->belongsToMany(Place::class,'city_place')->select(array('id', 'title'));
}
controller code:
public function ofcity($id)
{
$city=City::findOrFail($id);
return new CityResource( $city->place()->get());
}
enter image description here
You must indicate the name of the table in front of the fields.
model Place code:
protected $columns = ['places.id', 'places.title']; //all column for select
public function scopeExclude($query, $value = [])
{
return $query->select(\array_diff($this->columns, (array) $value));
}
model City code:
public function place()
{
return $this->belongsToMany(Place::class,'city_place', 'city_id', 'place_id');
}
controller code:
public function ofcity($id)
{
$cities = City::findOrFail($id)->place()->exclude(['featured_image'])->get()->toArray();
return response()->json(['cities' => $cities], 200);
}
In exclude skip all the fields that need not to be shown.
Thanks everyone here helping me out but none of the above solution worked..I figured it out after trying different functions and spending hours on this.
model Place code:
public function place(){
return $this->belongsToMany(Place::class,'city_place','city_id','place_id')->select(array('places.id', 'places.title'));
}
controller code:
public function ofcity($id)
{
$city=City::findOrFail($id);
return new CityResource( $city->place()->get()->map(function ($item,$key) {
return ['id' => $item['id'],'title'=>$item['title']];
})
);

Laravel collection returning duplicate

Hi I am trying to return a Users Schedule where greater than or equal to todays date, or if that schedule contains a relation (ScheduledYard) that is not complete:
$schedules = Auth::user()
->schedules()
->where('due_at', '>=' , Carbon::now())
->orWhereHas('scheduledYards', function ($query) {
$query->where('completed', false);
})->get();
$schedules = $schedules->sortBy('due_at');
return ScheduleResource::collection($schedules);
This works perfectly fine, but for some reason it is duplicating this for each user it is attached to. i.e if 3 users have this schedule then it is returned 3 times for the Authenticated user.
User Model:
public function schedules()
{
return $this->belongsToMany('App\Models\Schedule');
}
Schedule Model:
public function users() {
return $this->belongsToMany('App\Models\User');
}
If I remove the orWhereHas() it will return a single object. What am I doing wrong here and how can I return a single object based on the requirements above?
In Schedule Model, you need to change the relationship type. i assuming its one to many relationship between users and schedules. in that case change users() to user() and method body as below.
public function user() {
return $this->belongsTo('App\Models\User');
}
In User model the correct relationship should be defined as:
public function schedules()
{
return $this->belongsTo('App\Models\Schedule');
}
Please try with distinct. For example,
public function schedules()
{
return $this->belongsToMany('App\Models\Schedule')->**distinct**();
}

How to filter a polymorphic relationship to only return a specific model?

I'm having an issue with defining a function to filter a polymorphic relationship and only return a specific model. I'll try to explain below:
Say I have three models: A, B, and C. Model A can belong to either of the other two models. Say we're using the polymorphism field name of recipient, so on our model A database schema we have recipient_type and recipient_id.
On model A, I have a the default function called recipient, defined like so:
public function recipient()
{
return $this->morphTo();
}
However, I want a function called b() which will return a relationship so that it can be used with a query builder using the with() function. The idea being I can call $a->b and it will either return an instance of B or null, depending on whether the instance of A belongs to and instance of B...
Sorry this has been a bit of a mouthful..
Appreciate all the help I can get with this one!
Cheers!
You can define it like this
Model A (define accessor)
public function recipient()
{
return $this->morphTo();
}
public function bRelation()
{
return $this->belongsTo(B::class, 'recipient_id', 'id');
}
public function cRelation()
{
return $this->belongsTo(C::class, 'recipient_id', 'id');
}
public function getBAttribute(){ //define accessor
if($this->recipient_type == 'App\B') return $this->bRelation;
return null;
}
public function getCAttribute(){ //define accessor
if($this->recipient_type == 'App\C') return $this->cRelation;
return null;
}
Now use it with eager loading
$records = A::with('bRelation', 'cRelation')->get();
foreach($records as $a){
dd($a->b); //it will return you either instance of `B` or `null`
}
Works well with accessors:
public function getIssueAttribute()
{
if($this->commentable_type == 'issues') return $this->commentable;
return null;
}
public function getProjectAttribute()
{
if($this->commentable_type == 'projects') return $this->commentable;
return null;
}
public function commentable()
{
return $this->morphTo('commentable');
}

Laravel 4 Eloquent relations query

I have a project with main table 'Qsos' and bunch of relations. Now when I try to create advanced search I don't really know how to query all relations at the same time. Qso model has following:
public function band()
{
return $this->belongsTo('Band');
}
public function mode()
{
return $this->belongsTo('Mode');
}
public function prefixes()
{
return $this->belongsToMany('Prefix');
}
public function user()
{
return $this->belongsTo('User');
}
public function customization() {
return $this->hasOne('Customization');
}
Then I have SearchController with following code that has to return collection of all Qsos following required conditions:
$qsos = Qso::withUser($currentUser->id)
->join('prefix_qso','qsos.id','=','prefix_qso.qso_id')
->join('prefixes','prefixes.id','=','prefix_qso.prefix_id')
->where('prefixes.territory','like',$qTerritory)
->withBand($qBand)
->withMode($qMode)
->where('call','like','%'.$input['qCall'].'%')
->orderBy('qsos.id','DESC')
->paginate('20');
And then in view I need to call $qso->prefixes->first() and $qso->prefixes->last() (Qso and Prefix has manyToMany relation) but both return null. What is wrong?
Here is the eloquent code that I found working but taking VERY long time to process:
$qsos = Qso::withUser($currentUser->id)
->with('prefixes')
->withBand($qBand)
->withMode($qMode)
->where('call','like','%'.$input['qCall'].'%')
->whereHas('prefixes', function($q) use ($qTerritory) {
$q->where('territory','like',$qTerritory);
})
->orderBy('qsos.id','DESC')
->paginate('20');

Resources