Eager loading related model using query scope - laravel

Say I have a model Box which holds many widgets. The widgets can be active or inactive (boolean). The Widget model has a query scope which can filter results:
models/box.php:
class Box extends Eloquent
{
public function widgets()
{
return $this->hasMany('Widget');
}
}
models/widget.php:
class Widget extends Eloquent {
public function box()
{
return $this->belongsTo('Box');
}
public function scopeActive($query)
{
return $query->whereActive(true);
}
}
Query scopes make it easy to get all widgets for a given box:
$box_widgets = Box::find($box_id)->widgets()->active()->get();
// returns an Eloquent\Collection containing a filtered array of widgets
But how can I use scopeActive to eliminate this eager loading with method's conditional function?
$boxes = Box::with(array('widgets', function ($q)
{
$q->active();
}))->get();
It seems like there's probably a shorthand for accessing a relation's scope, something like Box::with('widgets->active') or Box::with('widgets.active') but I haven't been able to find it.

Suppose most of the time you want only active widgets, so I suggest:
public function widgets()
{
return $this->hasMany('Widget')->whereActive(true);
}
public function widgetsDisabled()
{
return $this->hasMany('Widget')->whereActive(false);
}
You can setup up more, for example for loading all at once, like you have now.
Then eager load as easily as that:
Box::with('widgets')... // loads only active

Related

laravel call to undefined method addEagerConstraints()

I have two models
Post.php
id
post
show_id
type = 'movie' or 'tv'
Show.php
id // this only auto increment counter ids
show_id
show_type = 'movie' or 'tv'
the thing is show can be either tv or movie and may two with the same show_id for exmaple one tv could have a show_id of 10 and also one movie can have it but the types are diffrent
i have in post model
public function show(){
return $this->belongsTo('App\Show', 'show_id');
}
in show model
public function post(){
return $this->hasMany('App\Post', 'id');
}
this relationship get the first show with matching show id it sees, wheather its a movie or tv, i want to restrict it to match type column on both sides
post.php:
public function show() {
return $this->belongsTo('App\Show', 'show_id', 'show_id')
->where('type', $this->type);
}
show.php
public function posts() {
return $this->hasMany('App\Post', 'show_id', 'show_id')
->where('type', $this->show_type);
}
UPDATE (the code above does not work!)
Trying to use where clauses (like in the example below) won't work when eager loading the relationship because at the time the relationship is processed $this->f2 is null.
Read more here: Compoships
I just came accross a package https://github.com/topclaudy/compoships
what it does it allows creating relationships based on more than one FK, which laravel doesnt support by default
I think what you're looking for is a polymorphic relation. Instead of having a model that may be one of two "types" there should probably be two separate models on the same relation. For example:
class Post
{
public function Show()
{
return $this->morphTo();
}
}
class TvShow
{
public function Post()
{
return $this->morphMany('App\Post', 'show');
}
}
class Movie
{
public function Post()
{
return $this->morphMany('App\Post', 'show');
}
}
Then your posts table would have a show_id and show_type field that would relate to either a tv show or movie. Check out the docs for more info, I'm not sure of the rest of your project so I'm not 100% this will fit but anytime you start putting "_type" fields in your table you should question whether or not you should be using a polymorphic relation. This will also likely keep your models cleaner and free of a bunch of if statements as you realize there are other differences between movies and shows and how you handle them.
https://laravel.com/docs/5.7/eloquent-relationships#polymorphic-relations

Access Method in a hasManyThorugh

I have 4 tables,
props, listing, offers, contact
props has many listing, listing belongs to props
public function listings()
{
return $this->hasMany('App\Models\Listing\Listing');
}
offer belongs to listing,
public function property()
{
return $this->belongsTo('App\Models\Property\Property')->with('owners');
}
then
offer belongsToMany contact trough offer_contact table
public function buyers()
{
return $this->belongsToMany(Contact::class, 'offer_contact', 'offer_id', 'contact_id')->with('primary_email');
}
My question is, how to access buyers()?
Something like $props->buyers()
In props model, what I did is
return $this->hasManyThrough('App\Models\Offer\Offer', 'App\Models\Listing\Listing');
You cannot. You may use nested iterations to get properties, listings belongs to each property, offers belongs to each listing and then customers belonging with the offer.
Alternatively, you may use the raw query to get the desired result using DB::statement();
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class Property extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function buyers() {
return $this->hasManyDeep(Contact::class, [Listing::class, Offer::class, 'offer_contact']);
}
}

How to eager load a relation with a condition in Eloquent?

I have a relation that can be inherited from a parent if not set for the object itself.
For an example setup let's say we have events that have a venue.
class Event extends Model
{
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function activities()
{
return $this->hasMany('App\Activity');
}
}
And there are activities in the events that mostly take place in the same venue, but sometimes could be elsewhere while still belonging to the same event.
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
if ($this->venue_id)
return $this->belongsTo('App\Venue');
return $this->event->venue();
}
}
If I simply request activities for an event and work with them it is fine. But if I try to eager load the venues for activities, I only get the ones that are set directly on the activity, never requesting one from parent.
$activities = $event->activities;
$activities->load('venue'); // Works correctly without this line
foreach ($activities as $activity)
if ($activity->venue) // Doesn't take venue from the parent (event)
echo $activity->venue->name; //Only shows if venue_id is set on activity
Is there any chance to fix the relations so I could load them in bulk?
By their very nature, eager loaded relationships do not have the relationship method run for each parent model. If they did, you would have the N+1 issue, which is exactly what eager loading is meant to solve.
The relationship method is run once on the model that is used to start the query. This gets the base query to run, and then all of the parent model ids are injected into that query.
In order to do what you want, you need to change things up a little bit. First, your Activity can be directly related to venues, so setup that relationship without any conditions. Next, create an accessor method that will return the proper venue for the Activity.
So, your code would look something like:
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function getActivityVenueAttribute()
{
return $this->venue ?? $this->event->venue ?? null;
}
}
The other option would be to always assign the venue_id on the Activity, even if it is the same as the Event venue_id. Then you don't need to worry about the venue id missing on the activity.

How to setup conditional relationship on Eloquent

I have this (simplified) table structure:
users
- id
- type (institutions or agents)
institutions_profile
- id
- user_id
- name
agents_profile
- id
- user_id
- name
And I need to create a profile relationship on the Users model, but the following doesn't work:
class User extends Model
{
public function profile()
{
if ($this->$type === 'agents')
return $this->hasOne('AgentProfile');
else
return $this->hasOne('InstitutionProfile');
}
}
How could I achieve something like that?
Lets take a different approach in solving your problem. First lets setup relationship for the various models respectively.
class User extends Model
{
public function agentProfile()
{
return $this->hasOne(AgentProfile::class);
}
public function institutionProfile()
{
return $this->hasOne(InstitutionProfile::class);
}
public function schoolProfile()
{
return $this->hasOne(SchoolProfile::class);
}
public function academyProfile()
{
return $this->hasOne(AcademyProfile::class);
}
// create scope to select the profile that you want
// you can even pass the type as a second argument to the
// scope if you want
public function scopeProfile($query)
{
return $query
->when($this->type === 'agents',function($q){
return $q->with('agentProfile');
})
->when($this->type === 'school',function($q){
return $q->with('schoolProfile');
})
->when($this->type === 'academy',function($q){
return $q->with('academyProfile');
},function($q){
return $q->with('institutionProfile');
});
}
}
Now you can access your profile like this
User::profile()->first();
This should give you the right profile. Hope it helps.
you can do this by use another method please check this:
a blog Post and Video model could share a polymorphic relation to a
Tag model. Using a many-to-many polymorphic relation allows you to
have a single list of unique tags that are shared across blog posts
and videos. First, let's examine the table structure:
https://laravel.com/docs/5.4/eloquent-relationships#many-to-many-polymorphic-relations
Looks like that should be $this->type rather than $this->$type - since type is a property, not a variable.

Laravel 5 Polymorphic Relationship

Not entirely clear on this yet as it would be the first time I'm tinkering with this. My table structure is as follows:
questions
- id
- type_id
multiple_choice_options
- id
- question_id
drag_and_drop_options
- id
- question_id
The type_id field on the questions table determines which options table to load from. So essentially I'd like to setup a relationship on the Question model as follows:
class Question extends Model {
public function options() {
// not sure what to return here?
}
}
And for the option models would this be the correct inverse definition?
class MultipleChoiceOption extends Model {
public function question() {
return $this->belongsTo(Question::class);
}
}
class DragAndDropOptions extends Model {
public function question() {
return $this->belongsTo(Question::class);
}
}
How do I set this up to work with polymorphic relationships?
You could simply build a switch in the options() relation:
class Question extends Model {
public function options() {
if ($type_id === 'multiple') {
return $this->hasMany(MultipleChoiceOption::class);
} else {
return $this->hasMany(DragAndDropOptions::class);
}
}
}
Careful though with these magic relations, you'll need additional type checks and careful coding in your app.
Check out the Laravel Docs on Polymorphism, there are some functions you could leverage with a slightly different data structure as such:
questions
- id
- optionable_id
- optionable_type
multiple_choice_options
- id
drag_and_drop_options
- id
Models:
class Question extends Model {
public function optionable() {
return $this->morphTo();
}
}
class MultipleChoiceOption extends Model {
public function question() {
return $this->morphMany('App\Question', 'optionable');
}
}
class DragAndDropOptions extends Model {
public function question() {
return $this->morphMany('App\Question', 'optionable');
}
}
Note, the optionable_type column will contain the class name of the owning model.
A side note, your data structure relationship logic seems a little strange to me but may still serve what you are trying to do. I would've probably had models Question, Option, QuestionType so that options are independent of question and question type. This would enable you to change the question type without having to change the options.
Considering what you are trying to achieve:
a)An option can only belong to a one question,
b)A question can have many options,
I think a cleaner solution might involve adjusting your database design,
questions
- id
- body
- question_type_id
question_type
- id
- name
options
- id
- name
- question_id
class question extends Model {
public function options() {
$this->hasMany('App\option','question_id')
}
}
class option extends Model {
public function question() {
return $this->belongsTo('App\question');
}
}
your question_type table would then contain muiltiple_choice,drag and drop, etc. therefore there would be no need for you to use an 'if' statement or 'switch', which makes your code cleaner. you also do not need to edit your code whenever you add a new question type.

Resources