Cat Named Dog
Cat Named Dog

Reputation: 1698

Model appends including entire relationship in query

Edit: I was able to see where the relations are being included in my response, but I still don't know why.

On my Customer model, I have:

protected $appends = [
        'nps',
        'left_feedback',
        'full_name',
        'url'
    ];

The accessors are as follows:

/**
     * Accessor
     */
    public function getNpsAttribute() {
        if ($this->reviews->count() > 0) {
            return $this->reviews->first()->nps;
        } else {
            return "n/a";
        }
    }



    /**
     * Accessor
     */
    public function getLeftFeedbackAttribute() {
        if ($this->reviews && $this->reviews->count() > 0 && $this->reviews->first()->feedback != null) {
            return "Yes";
        } else {
            return "No";
        }
    }

    /**
     * Accessor
     */
    public function getFullNameAttribute() {
       return ucwords($this->first_name . ' ' . $this->last_name);
    }


    /**
     * Accessor
     */
    public function getUrlAttribute() {
        $location = $this->location;
        $company = $location->company;
        $account_id = $company->account->id;

        return route('customers.show', ['account_id' => $account_id, 'company' => $company, 'location' => $location, 'customer' => $this]);
       
     }

So if I comment out the $appends property, I get the response I originally wanted with customer not returning all the relations in my response.

But I do want those appended fields on my Customer object. I don't understand why it would include all relations it's using in the response. I'm returning specific strings.

So is there a way to keep my $appends and not have all the relations it's using in the accessors from being included?


Original Question:

I am querying reviews which belongsTo a customer. I want to include the customer relation as part of the review, but I do not want to include the customer relations.

$reviews = $reviews->with(['customer' => function($query) {
            $query->setEagerLoads([]);
            $query->select('id', 'location_id', 'first_name', 'last_name');
        }]);

$query->setEagerLoads([]); doesn't work in this case.

I've tried $query->without('location'); too, but it still gets included

And I should note I don't have the $with property on the model populated with anything.

Here is the Review model relation:

    public function customer() {
        return $this->belongsTo('App\Customer');
    }

Here is the Customer model relation:

    public function reviews() {
        return $this->hasMany('App\Review');
    }
    


    // I dont want these to be included
    public function location() {
        return $this->belongsTo('App\Location');
    }

    public function reviewRequests() {
        return $this->hasMany('App\ReviewRequest');
    }

In the response, it will look something like:

'review' => [
 'id'=> '1'
 'customer => [
   'somecol' => 'test',
   'somecolagain' => 'test',
   'relation' => [
      'relation' => [

      ]
   ],
   'relation' => [
       'somecol' => 'sdffdssdf'
    ]
  ]
]

So a chain of relations ends up being loaded and I don't want them.

Upvotes: 1

Views: 2291

Answers (2)

matiaslauriti
matiaslauriti

Reputation: 8082

As you said in one comment on the main question, you are getting the relations due to the appended accessors.

Let me show you how it should be done (I am going to copy paste your code and simply edit some parts, but you can still copy paste my code and place it in yours and will work the same way but prevent adding the relations) and then let me explain why is this happening:

/**
 * Accessor
 */
public function getNpsAttribute() {
    if ($this->reviews()->count() > 0) {
        return $this->reviews()->first()->nps;
    } else {
        return "n/a";
    }
}



/**
 * Accessor
 */
public function getLeftFeedbackAttribute() {
    return $this->reviews()->count() > 0 && 
           $this->reviews()->first()->feedback != null 
        ? "Yes" 
        : "No";
}

/**
 * Accessor
 */
public function getFullNameAttribute() {
   return ucwords($this->first_name . ' ' . $this->last_name);
}


/**
 * Accessor
 */
public function getUrlAttribute() {
    $location = $this->location()->first();
    $company = $location->company;
    $account_id = $company->account->id;

    return route('customers.show', ['account_id' => $account_id, 'company' => $company, 'location' => $location, 'customer' => $this]);
   
 }

As you can see, I have changed any $this->relation to $this->relation()->first() or $this->relation->get().

If you access any Model's relation as $this->relation it will add it to the eager load (loaded) so it will really get the relation data and store it in the Model's data so next time you do $this->relation again it does not have to go to the DB and query again.

So, to prevent that, you have to access the relation as $this->relation(), that will return a query builder, then you can do ->count() or ->exists() or ->get() or ->first() or any other valid query builder method, but accessing the relation as query builder will prevent on getting the data and store it the model (I know doing ->get() or ->first() will get the data, but you are not directly getting it through the model, you are getting it through the query builder relation, that is different).

This way you will prevent on storing the data on the model, hence giving you problems.

You can also use API Resources, it is used to map a Model or Collection to a desired output.

One last thing, if you can use $this->relation()->exists() instead of $this->relation()->count() > 0 it will help on doing it faster, mostly any DB is faster on looking if data exists (count >= 1) than really counting all the entries it has, so it is faster + more performant on using exists.

Upvotes: 5

Majdiden
Majdiden

Reputation: 84

Try :

$review->with(‘customer:id,location_id,first_name,last_name’)->get();

Or :

 $review->withOnly(‘customer:id,location_id,first_name,last_name’)->get();

Upvotes: 0

Related Questions