Scope by custom attribute or excluding data by scope - laravel

I have a Model User that has a polymorphic relation to table Relationships with Pivot column relationship_level.
public function activities()
{
return $this->morphedByMany('App\Activity', 'relationship')->withPivot('relationship_level')->withTimestamps();
}
public function relationships()
{
return $this->hasMany('App\Relationship');
}
I want one the following:
I want scopeNoCitizens which to exclude everyone who has a relationship_level Citizen no matter what other relationships he has.
I've tried:
scopeNoCitizens($query) {
$query->whereHas('relationship', function($query) {
$query->where('relationship_level', '!=', 'Citizen');
});
}
This doesn't work, maybe because all users have many levels and I want everyone who has even one record among many that has level citizen to be excluded from the collection.
I also have a custom attribute getHighestRoleAttribute, but as far as I understand you can't filter by accessor/custom attribute in a scope.
What I currently do is "filter" the collection after get(), but I have to manually paginate after that and do that on numerous places in the app.

Related

Can I create a double-morphed many-to-many relationship in Laravel?

I have two models: Product and Group, which I want to both give a many-to-many 'shown' and a 'recommended' relation with itself and the other (so both have has to be morphed in both ways).
So a 'shown' resource of either type has a set of 'recommended' resources of either type (the Groups are actually acting as a container for grouped Products).
The table should be pretty straightforward:
Schema::create('recommendables', function (Blueprint $table) {
$table->id();
$table->morphs('shown');
$table->morphs('recommendable');
$table->timestamps();
});
Then relations are defined in the trait 'RecommendableTrait' which is then given to both classes. As far as these relationships are concerned both classes are then handled pretty much the same later on (even to be merged into a single collection), so there is merit in trying to do it this way.
The problem is that the following relation definitions won't work, because it will look for group_id if I call the morphed relations on Group, similarly with `morphedByMany'.
trait RecommendableTrait {
// RECOMMENDED GROUPS
public function recommendedGroups()
{
return $this->morphToMany(Group::class, 'recommendable', 'recommendables')
->withTimestamps();
}
// RECOMMENDED PRODUCTS
public function recommendedProducts()
{
return $this->morphToMany(Product::class, 'recommendable', 'recommendables')
->withTimestamps();
}
// SHOWN GROUPS
public function shownGroups()
{
return $this->morphToMany(Group::class, 'shown', 'recommendables')
->withTimestamps();
}
// SHOWN PRODUCTS
public function shownProducts()
{
return $this->morphToMany(Product::class, 'shown', 'recommendables')
->withTimestamps();
}
}
Now I know, I'm looking to create a bit of a weird relation tangle here. But I'm hoping defining a double-morphed many-to-many is possible in Laravel?
If not, I guess I'll have to either split it up in two single-morphed many-to-Many relationship sets for both classes. Which would be annoying because of the code that goes on top of these relations would have to be needlessly duplicated. Or I'll have to forego relations and just DB query the results directly. Thus giving up some of Eloquence's convenience, but at least allowing me to keep the code confined to a single shared trait.

Laravel Eloquent Relation belongsTo update

I am trying to update/delete/create in belongsTo relations.
Company has many sports
sports is belonging to Company
Here is two models.
class CompanySports
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
class Company
public function sports()
{
return $this->hasMany(CompanySports::class,"company_id","id");
}
}
at controller, when sports is added or modified or remove, what is the best practice to update?
i know that many to many, sync can be used. In this, what is the best solution? Should i compare everytime after loading all from database which is not good practice i believe.
From your code, I would first recommend putting your models in separate files, and ensuring they are singular. If you use the artisan make:model command to generate the stubs, it should do this for you.
// app/CompanySport.php // <-- NOTE singular
class CompanySport // <-- NOTE singular
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
}
// app/Company.php
class Company {
public function sports()
{
return $this->hasMany(CompanySport::class,"company_id","id"); // singular
}
}
From there, I find it helpful to build helper methods in the various classes so that the grammar sounds natural and more importantly, belongs to the model. For example:
// app/Company.php
class Company
{
...
public function addSport(CompanySport $sport)
{
$this->sports()->save($sport);
}
public function removeSport(CompanySport $sport)
{
$this->sports()->find($sport->id)->delete();
}
}
These helper functions can then be easily called from anywhere, e.g. controller:
// CompanySportsController.php
public function store(Company $company, CompanySport $sport)
{
$company->addSport($sport);
return redirect('/company/' . $company->id);
}
If you are using these helpers, there is no comparing or sync to be done since you are only using a one to many relationship. Eloquent does everything for you.
Also, I've found this cheatsheet particularly helpful when building out the initial relationships and scaffolding of a new app.
While adding new record of Company Model, you need not to do anything as there is no child for it yet.
While updating an instance of a Company model, again you need not to update anything on its children. As relationship are based on id(primary key) which I believe you don't change while updating.
And now for deleting there are some questions. Do you want to delete the children when the parent is deleting? If so, you can use ON DELETE CASCADE which you can set up in migration like
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
in your spors table.
Well you can make your own function too like answered in here
Well if you don't want to delete the children, you can use softdelete on your Model. set up the relations then like
CompanySports
public function company()
{
return $this->belongsTo(Company::class, "company_id","id")->withTrashed();
}
This way you can get the parent of a children without any error though the parent is deleted.

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

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.

Creating a Many-to-many relationship in Laravel with additional data

I have in my database a pivot table that stores extra information. It has 2 foreign keys, and an additional field. Here's what it looks like:
EventTeam
int event_id (fk)
int team_id (fk)
boolean home
The intent here is that an Event may have many teams (in fact, it must have at least 2, but that's not a database constraint), and a team may participate in many events. However, for each event-team relationship, I want to also track whether the team is considered the home team for that event.
How do I define my model with this in mind? Do I have an EventTeam model at all, or do I define a belongsToMany relationship in both the Team and Event models? If I need a separate model, what relationships do I define in it? If I don't, how do I add the boolean field to the pivot table that gets used? I really have no idea how to do this.
You dont need a EventTeam model per se, but it could come in handy for seeders or if you are going to attach models to your EventTeam connection anywhere else in your app. This should work:
Event model:
public function teams()
{
return $this->belongsToMany('Team');
}
Team model:
public function events()
{
return $this->belongsToMany('Event');
}
For the extra boolean you can use ->withPivot().
$this->belongsToMany('Event')->withPivot('is_home');
See http://laravel.com/docs/eloquent#working-with-pivot-tables for more info.
Updated answers:
1) I would put it in both models so you can access the pivot data from both sides without a problem.
2) It should be to column name indeed.
3) Like i said its not really needed for you in this situation, but you could do this:
EventTeam model:
public function event()
{
return $this->belongsTo('Event');
}
public function team()
{
return $this->belongsTo('Team');
}
Add withPivot('home') on your relations definitions, then you can access it like this:
$team->events->first()->pivot->home; // 0/1
$event->teams->first()->pivot->home; // 0/1
first is just an example of getting single related model here.
Now, next thing is adding that value to the relation:
$team = Team::find($id);
$event = Event::find($eventId);
$team->events()->attach($event, ['home' => 1]);
// or
$team->events()->attach($eventId, ['home' => 1]);
// or using sync
$event->teams()->sync([1,5,15], ['home' => 0]);
Another thing is querying that field:
// load first team and related events, that the team hosts
$team = Team::with(['events'=>function ($q) {
$q->wherePivot('home', 1);
}])->first();
// load only teams that are hosts for any event
$hostTeams = Team::whereHas('events', function ($q) {
// wherePivot won't work here!
$q->where('event_team.home', 1);
})->get();
and so on.

Resources