Eloquent with diferent relationships for same table - laravel

i need some hints and what is the best way and pratice with laravel to solve this problem!
I have a main table called colors with the fields category_id and type and i have in my models 2 relationships for the category_id, the bluecategory and redcategory.
In some cases i use the id from bluecategory and in other exemples i use the redcategory id.
Now in one page i want to shows all the results from colors, but i can't do this $data->bluecategory or $data->redcategory because i dont'no what record is using what relationship.
My ideia was using a function to send parameters category_id and type and inside the function discover what is the correspondente relashion and return the correct result.
But how i can handle this?
Sorry its a bit confuse!

You could add a scope to your modal and than chain it to your existing query.
Example
public function scopeName($query, $catID, $type)
{
return $query->where('category_id, $catID)
->where('type', $type);
}

Related

Wrong ID returned from local Eloquent scope

In my Laravel project I've got a model (and an underlying table) for lessons. Now I'm trying to write a local scope for returning all lessons that have been finished by a particular user. The definition of "finished" is that there exists a row in a table named "lesson_results" with this lesson's ID and the users ID.
My scope currently looks like this:
public function scopeFinished($query, User $user)
{
return $query->join('lesson_results', function($join) use($user)
{
$join->on('lesson_results.lesson_id', '=', 'lessons.id')
->where("user_id", $user->id);
});
}
This kinda works. When I do a Lesson::finished($user)->get() I get out the correct lessons for that user. However the lessons in the collection returned all have the wrong ID's! The ID's I see are the ID's from the lesson_results table. So when I check $lesson->id from one of the returned items I don't get the ID from that lesson, but the ID from the corresponding row in the lesson_results table.
I've checked in mysql and the full query sent from Laravel is the following
select * from `lessons` inner join `lesson_results` on `lesson_results`.`lesson_id` = `lessons`.`id` and `user_id` = 53
This query DO return two columns named id (the one from the lessons table and the one from the lesson_results table) and it seems Laravel is using the wrong one for the result returned.
I don't know if I'm going about this the wrong way or if it's a bug somewhere?
This is on Laravel 7.6.1.
edit: Ok, I think I actually solved it now. Not really sure though if it's a real solution or just a workaround. I added a select() call so the return row now is
return $query->select('lessons.*')->join('lesson_results', function($join) use($user)
...which makes it only return the stuff from the lessons table. But should that really be needed?
One of the same column names will be covered by the other.
Solution 1:
Specify the table with the column, and alias the other table's column if it has same column name.
Lesson::finished($user)->select('lessons.*', 'lesson_results.id AS lesson_result_id', 'lesson_results.column1', 'lesson_results.column2',...)->get();
Solution 2:
Or you can use Eloquent-Builder eager-loading whereHas,(Assuming you have build the relationship between model Lesson and model LessonResult)
public function scopeFinished($query, User $user)
{
return $query->whereHas('lessonResults', function($query) use($user)
{
$query->where("user_id", $user->id);
});
}
So you can get lesson like this:
Lesson::finished($user)->get();

sort laravel eloquent by custom (appended) attribute

I have a model with a custom attribute like this
public function getOpenStatusAttribute()
{
//some logic...
//returns '1-order' or '2-pre-order' or '3-closed'
}
Now i want to sort the collection in the eloquent query. I use order by name etc.. those are columns in the table but i want to order it by the custom attribute first, and then by name etc..
is it possible to do this in the query? or do i have to loop the collection and do some resorting ?
ok so the solution is:
->sortBy(['open_status'])->sortBy(['name']);
after the ->get()
hope it can help someone in the future

Query hasMany relation with slug instead of id?

I'm trying to query a category and all of its channels based on a slug.
Works
\App\Category::find(1)->channels()->get();
Doesn't Work
\App\Category::where('slug','animals')->channels()->get()
BadMethodCallException with message 'Method Illuminate/Database/Query/Builder::channels does not exist.'
Relationship on Category Model
public function channels()
{
return $this->hasMany(Channel::class,'category_id');
}
Assuming that you correctly have your slug field set-up in migration and model attributes (It looks like so from the Exception message).
Doing
\App\Category::find(1)->channels()->get();
is under the hood equivalent to
\App\Category::where('category_id', 1)->first()->channels()->get();
So what you are requiring, is to get the categories to actually execute the query and then be able to retrieve the channels from the hydrated model.
\App\Category::where('slug', 'animals')->first()->channels()->get();
should work as well as
\App\Category::where('slug', 'animals')->first()->channels; // calling as attribute will perform the get() on the relationship
Also take note that you may take advantage of other methods like with() for eager loading the relationship, first() to perform a get and ensure you take only one instance, and calling the relationship as attribute as shown above. Refer to the docs
there are so many possible answer in your question .. be more specific .. but here's the list of sample queries that may help you ..
GETTING ALL THE CATEGORY WITH ITS CHANNELS WHERE CATEGORY SLUG = ANIMAL
Category::with(['channels'])->where('slug', 'animal')->get();
GETTING ALL THE CATEGORY WITH ITS CHANNELS WHERE HAS A CHANNEL SLUG = ANIMAL
Category::with(['channels'])->whereHas('channels'=>function($q){
$q->where('slug','animal');
})->get();
GETTING ALL THE CATEGORY WITH ONLY CHANNELS THAT HAS A SLUG = ANIMAL
Category::with(['channels'=>function($q){
$q->where('slug','animal');
}])->get();

Laravel polymorphic hasMany relationship

From Laravel's docs, the model polymorphism is defined as follows:
Polymorphic relations allow a model to belong to more than one other model on a single association
Sounds like it's designed to work with belongsTo instead of hasMany side. Here's a scenario that I want to achieve:
In my system, there are many project types, each projec type will have its own invoice field layout. Let's say we have a Project model that has a type field, whose value could be contract or part-time. We have another two tables called ContractInvoice and PartTimeInvoice to define their respective field layout, both of these invoice tables have a project_id referencing a project record. What I want to do is I want a universal interface to retrieve all invoices given a project, something like $project->invoices.
Current solution
I can't figure out how to achieve this via polymorphism. So what I am currently doing is kind silly, using a switch statement in my invoice() method on Project model class:
switch ($this->type) {
case 'contract':
$model = 'App\ContractInvoice';
break;
case 'part-time':
$model = 'App\PartTimeInvoice';
break;
}
return $this->hasMany($model);
I feel like there must be a better way to do this. Can someone please shed some light?
I don't see how a polymorphic relationship would be beneficial in this case. If you had different project type models and a single invoices table, then the invoices could morphTo the projects. But as you've described it, the switch statement sounds like it is adequate. You could achieve the same means using when conditionals like:
public function invoices()
{
return $this->when($this->type === 'contract', function () {
return $this->hasMany(ContractInvoice::class);
})->when($this->type === 'part-time', function () {
return $this->hasMany(PartTimeInvoice::class);
});
}
The type attribute on the Project model and the separate invoice tables are defining a rigid relationship between them, which goes against the idea of polymorphism. Think likes for comments and posts.

eloquent filter result based on foreign table attribute

I'm using laravel and eloquent.
Actually I have problems filtering results from a table based on conditions on another table's attributes.
I have 3 tables:
venue
city
here are the relationships:
a city has many locations and a location belongs to a city.
a location belongs to a venue and a venue has one location.
I have a city_id attribute on locations table, which you may figured out from relationships.
The question is simple:
how can I get those venues which belong to a specific city?
the eloquent query I expect looks like this:
$venues=Venue::with('location')->where('location.city_id',$city->getKey());
Of course that's not gonna work, but seems like this is common task and there would be an eloquent command for it.
Thanks!
A couple of options:
$venues = Venue::whereIn('location_id', Location::whereCityId($city->id)->get->lists('id'))
->get();
Or possibly using whereHas:
$venues = Venue::whereHas('location', function($query) use ($city) {
$query->whereCityId($city->id);
})->get();
It is important to remember that each eloquent query returns a collection, and hence you can use "collection methods" on the result. So as said in other answers, you need a Eager Loading which you ask for the attribute you want to sort on from another table based on your relationship and then on the result, which is a collection, you either use "sortBy" or "sortByDesc" methods.
You can look at an example below:
class Post extends Model {
// imagine timpestamp table: id, publish, delete,
// timestampable_id, timestampble_type
public function timestamp()
{
return $this->morphOne(Timestamp::class, 'timestampable');
}
}
and then in the view side of the stuff:
$posts = App\Post::with('timestamp')->get(); // we make Eager Loading
$posts = $posts->sortByDesc('timestamp.publish');
return view('blog.index', compact('posts'));

Resources