several tables with many-to-one relationship - laravel

I'm using octobercms which uses eloquent models.
I have several tables (Books, Movies, Comics...) and I need to link them to an agegroup table. Every book (movie, comic) gets exactly one agegroup.
My first intention was this:
table agegroup:
id
...
table book:
id
agegroup_id
...
table movie:
id
agegroup_id
...
But I dont get how to put that into an Eloquent model.
Bonus question: there are other similar links (every book, movie etc. has exactly one section) where I need basically the same fields as in the agegroup table: should I reuse that (how?) or create another similar table?

Relationships don't have a single direction, they're two-ways, so one-to-many is the same as many-to-one, only the perspective changes. Meaning that from one perspective a model has many others, and from the other a model belongs to another model. Even the naming used in defining the relationships is very intuitive: hasMany and belongsTo. So here's how you would configure the relationship between a Book model and a AgeGroup model.
A book belongs to only one age group:
class Book extends Model
{
public $belongsTo = [
'ageGroup' => 'Acme\Blog\Models\AgeGroup'
];
}
Then you can get the age group of a book like this:
Book::find(1)->ageGroup->name; // I'm assuming age groups have names
The revers of that relationship is that an age group can have many books associated to it:
class AgeGroup extends Model
{
public $hasMany = [
'books' => 'Acme\Blog\Models\Book'
];
}
Then you can get all books that belong to an age group like so:
foreach (AgeGroup::find(1)->books as $book) {
// access book details like: $book->title;
}
The same logic applies to movies and whatever other entities that can have one age group.

Related

Broken Eloquent relationship with extended model

I have a model Asset with documents() { $this->hasMany(Document::class); } through a table data_asset_document. I extend Asset into multiple models, one of which is Equipment. In my seeder for Equipment, I attempt to create a Document bound to the Equipment record:
$asset = Equipment::create([...]);
$document = Document::create([
'name' => "$type Purchase Order",
'tracking_number' => app('incrementer')->current()
]);
$asset->documents()->save($document);
Eloquent produces this query:
update `data_document` set `equipment_id` = 1, `data_document`.`updated_at` = 2019-09-20 14:39:48 where `id` = 1
This is obviously incorrect, since data_document does not have an equipment_id column (Documents "belong to" several models besides Asset). How do I rewrite Asset::documents so that produces the correct mapping, even in its extensions? Or do I need to save my Document through a means other than Asset::documents?
Since your extended asset model is called Equipment, Laravel expects your foreign key to be called equipment_id. You will need to specify the actual foreign key of asset_id in your relationship.
https://laravel.com/docs/6.x/eloquent-relationships#one-to-many
hasMany
documents() {
$this->hasMany(Document::class, 'asset_id');
}
The problem is, I'm not convinced your relationship is really hasMany since you mention what looks like a pivot table data_asset_document as being involved. Many-to-many relationships, like mentioned in your title, would use the belongsToMany method.
https://laravel.com/docs/6.x/eloquent-relationships#many-to-many
https://laravel.com/docs/6.x/eloquent-relationships#has-many-through

Eloquent relation with two keys

I am working on a team sports statistics project in Laravel.
Here is a sketch of the main models regarding this question:
class Team
// belongsToMany matches
// belongsToMany people
class Match
// belongsToMany teams
// belongsToMany people
class Person
// belongsToMany matches
// belongsToMany teams
I have also made models for the pivot tables to work directly with them. I have some stuff like goals_scored, number, position etc. stored there and it's too clumsy to use methods for updating pivot.
class MatchTeam // Participation of a team in the match
// belongsTo match
// belongsTo team
class MatchPerson // Participation of a person in the match
// belongsTo match
// belongsTo team
// belongsTo person
Is there an Eloquent way to relate MatchTeam to MatchPerson? I don't want to introduce a direct match_team_id foreign key, I want to do this using the existing match_id and team_id fields on both of the tables.
Currently I have this in my MatchTeam model:
public function matchPeople() // the roster
{
return $this->hasMany('App\Stats\MatchPerson', 'team_id', 'team_id')->where('match_id', $this->match_id);
}
It works fine. However, I am very concerned that this code is not fair to the nature of the relation - they are not related based on team_id, they are related based on both fields. I could have also written it like this:
public function matchPeople() // the roster
{
return $this->hasMany('App\Stats\MatchPerson', 'match_id', 'match_id')->where('team_id', $this->team_id);
}
Is there a fair way in which I could specify relation based on two columns in Eloquent?
Of course, this question is more out of curiosity as the actual problem of making the method is conquered.
A usage example for the curious:
$teamA->goals_for = $teamA->matchPeople->sum('goals');

Model relationship in Laravel

I have 2 controllers & Models:
User Controller: (Model Relationship: $this->hasMany(Hero::Class);)
Hero Controller: Each hero has his own attributes, such as name, strength and life.
Model Relationship: ($this->belongsTo(User::class);)
Each user can own multiple heroes.
that means that USER ID: 1 may have 3 heroes: HERO ID 5, 20, 26..
My question: How to define the relationships like that and make laravel knows how to handle my user_heroes table?
The relationship i'm talking about is described in the following image:
How to I setup such kind of relationship in my laravel API?
If a User can have many heroes, and a Hero can also belong to many users, it is a many to many relationship. In Laravel the inverse of a many to many relationship is also a many to many relationship, and they are both described by belongsToMany().
https://laravel.com/docs/5.2/eloquent-relationships#many-to-many
So in your User model:
public function heros() {
return $this->belongsToMany(Hero::class);
}
And in your Hero model:
public function users() {
return $this->belongsToMany(User::class);
}
Laravel will assume the joining table is named hero_user, the 2 model names, singular, joined in alphabetical order. If you want to use user_heroes as you have in your image, you need to specify it:
return $this->belongsToMany(Hero::class, 'user_heroes');
(in both model methods).

Correct relationship in Laravel

I have four tables in database: groups, specialties, lessons, group_lesson. It's structures:
groups
id
specialty_id
name
specialties
id
name
lessons
id
specialty_id
group_lesson (UNIQUE INDEX lesson_id, group_id)
lesson_id
group_id
date
My models look like that for now:
class Group extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Lesson extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Specialty extends Eloquent {
public function lessons() {
return $this->hasMany('Lesson');
}
public function groups() {
return $this->hasMany('Group');
}
}
I need get additional fields in Group model look like that
Group - Eloquent model
name - string
lessons - collection of Lesson models of Group Specialty
date - date from group_lesson table
I've tried different relationships and combinations, but it's doesn't work. Please help me to write correct relationships.
You can use eager-loading to access relational data through relationships, and can even chain relationships further. As a rule of thumb, if you can draw a path to from 1 model to another through a relationship, you can eagerload all the relevant and relational data for that with chained eager-loads.
Laravel Eager Loading
As an example
$speciality_group = Speciality::with('group','lessons')->find($id);
Even though you are only getting a single instance of the speciality model, the related data is hasMany, meaning multiple records. You need to loop through these records using a foreach loop to access the relevant data for them, or alternitavely add additional closures in your initial query to load only a single related model.
foreach($speciality_group->group as $group)
{
echo $group->name;
}
You will need to do this for both instances where you want to display related information.

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