I have 2 tables in my database, a notifications table and a notifications_user table.
A notification can have many notification users. And a result in my notification model I have the following relationship,
public function user()
{
return $this->belongsToMany('User')->withPivot('is_read');
}
What I am wanting to achieve is to get all notifications that are unread (or is_read = 0), the is_read column is in the notification user table though and I cannot work out how to run a query on it becuase of that.
Current I have this,
$unread = Notification::has('user')->with('user')->get();
Now this pulls the relationship into the results, but takes no account of the is_read value.
Is there a way to select all the data from a table based on a value in its 1:n relationship.
You can query on the relationship by doing something like:
$unread = Notification::has('user')->with('user')->where('is_read','!=', 0)->get();
Check the docs: http://laravel.com/docs/5.1/eloquent-relationships#querying-relations
So while it may make sense to have the relation that notifications have many users it really doesn't make sense from an ownership. Notifications are one-time things while users persist in a way. So try to think of users as the base object and notifications as the abundant resource.
The goal here is to get the users notifications so out of this you have to choose whether to duplicate notifications for each user or have one notification for many users. In one case where it's universal notifications (admin panel maybe) and the other is notifications that are personal to the user. If you're doing the latter you don't really need a pivot table and just a notifications table.
User -> hasOne -> Notification
Notification -> belongsTo -> User
This enables you to really customize the notifications per-user than relying on maybe another table for read notifications you can just mark it as "read" in the row.
If however you need universal notifications the structure just implements a third table called a pivot as you know. (I noticed you have the class names pluralized which is not recommended)
User -> Notification_User -> Notification
For ease you also could just soft-delete the notification_user row or notification themselves. You can simply just say ->withTrashed()->limit(x) to get previous notifications.
This really simplifies the work done by the DB and your code. The personal notifications allows you to order by creation/update and deal with read in two ways, soft-deletion and IsRead variables.
Your code becomes as simple as this.
Auth::user()->notifications()
Your User class has the following (assuming standard naming schemes)
public function notifications()
{
//You're free to append other requirements here
return $this->hasMany('App\Notification','id','user_id');
}
The Notification class has the inverse
public function user()
{
//You're free to append other requirements here
return $this->belongsTo('App\User','user_id','id');
}
If for some reason you require to know all unread notifications universally just query the Notification table.
Notification::where('isRead','null')->get();
Alternatively you can lazy load the users for each notification or group by users in this case for whatever purpose you need.
If this helped you to your solution could you mark it as the answer?
You can use the wherePivot and orWherePivot functions provided by laravel for relations. Link
Related
We are just switching to laravel in my workplace and I still have problems with using more complicated relationships.
I have the following models:
Company
id
name
...
Domain
id
url
company_id -> belongs to Company
...
CommunicationEvent
id
name (like: OrderPlaced)
...
Email
id
name
subject
body
company_id -> belongs to Company
...
SMS
similary to Email
Every Company should be able to set the communication to be used in an Event and in an event multiple type of communications can be used. For example, if an OrderPlaced event fires they can choose to have both an email and an SMS to be sent. Moreover they can set a default email/sms for all of their domains in an event and just overwrite it for one of their domains to use a different one there.
My first shot was to make a pivot table with event_id, company_id, domain_id(nullable for default) and communicable_id/type. I made a test relationship like this into CommunicationEvent:
public function mails(){
return $this->morphToMany('App\Models\Mail', 'communicable', 'comm_event_comms', 'event_id', 'communicable_id')->where('comm_event_comms.company_id', 1);
}
And I put dummy data into the table, but $event->mails returns empty. Reading the documentation made me think I'm not on the right track.
Honestly, I don't really know how to properly connect these models so I can use them easyly. The only way I could make it work was to use DB::where... queries, but nothing with relationships. :(
Can you please help to build a relationship between these models?
I realized what was the problem with the relationship. I should have used morphedByMany instead of morphToMany. Thanks to miken32 I reread documentation and realized this on 3rd read.
The right relationship in communicationEvent:
public function mails(){
return $this->morphedByMany('App\Models\Communication\Mail', 'communicable', 'communicables', 'event_id')->where('communicables.company_id', DData::getCompanyId());
}
I also changed the name of the pivot table to communicables for convention.
Like that $event->mails()->attach($mail_id, ['company_id' => DData::getCompanyId()]); works perfectly to set default email communication for event. And setting domain specific communication works as well with additional domain_id data in attach.
I can use $event->mails as well for select.
The only problem that remained is detaching. As I read here:
...if you're storing other data on a "pivot" it's not really a pivot
anymore...
I don't think I will be able to solve that with a simple detach() method so I used db queries like this:
DB::table('communicables')->where([
['event_id', $event->id],
['domain_id', NULL],
['company_id', DData::getCompanyId()]
])->delete();
I will have to search for communicable type as well when I connect the SMS model, but I decided it is a good enough solution. Other solution would have been to make a separate model for comminicables table, but I didn't wanted that.
Suppose that I have a Notification Table that gets generated when a new log from another table is generated. Suppose I have 3 different logs with different purpose namely: sms_logs, call_logs, and appointment_logs.
I want to make a relationship to each logs without using sms_logs_id, call_logs_id and appointment_logs_id. Instead, I want to build only two columns, one for the type, and the other for the ID. So for example an sms log is generated with an id of 187, it will also generate a notification log with a notification_id of 187 and a type of "sms".
How will I be able to create that? Thank you!
Nice question.
You have to put only two fields in notifications table. foreign_id and log_type.
Whenever you add a log, you have to set log_type accordingly. Then add this relationship in your Notification model.
public function foreignModel()
{
switch($this->log_type){
case "call_log":
return $this->belongsTo('App\Call', 'foreign_id');
break;
}
}
I didn't tried it, but hope it will work fine.
If you are looking for something more dynamic and less robust than this then I don't think that it exists.
I was working on making a group functionality for my website which uses a many to many relationship between groups and users.
My User model looks like this:
public function groups(){
return $this->belongsToMany('App\Group')->withPivot('role')->withTimestamps();
}
My Groups model looks like this:
public function users(){
return $this->belongsToMany('App\User')->withPivot('role')->withTimestamps();
}
So my third column has the name of role which is a string variable and is set to a default of "member" for members of my group and I set it to "admin" for the actual user who creates a new group. But I want the admin to have the option of making multiple members admins as well which would require me to check weather the current current user who sent the request is an admin or not. If he is, then I wanna be able to take his request of making a member an admin which would require me to update the role for that particular "member" to an "admin".
In the laravel documentation it only shows you how to attach and detach data in a pivot table and else where I have only seen methods of retrieving data from the first two columns but how can I do the same for additional columns and also be able to update it using the updateExistingPivot method?
You could access the column simply using pivot e.g :
$user->pivot->role
Take a look at Retrieving Intermediate Table Columns in documentation Eloquent Relationships.
Hope this helps.
I have three tables: users, purchase_orders and approvals.
One purchase_order has to be approved by multiple users.
When a new purchase_order gets created, I also create 3 pending approvals belonging to that PO.
The approvals table has a field allowed_user_type that determines who can approve it.
I can't figure out, what is the Eloquent way of selecting the pending purchase orders that can be approved by a specific user, as these are determined from the approvals table.
So far I can pull the pending approvals from the approvals table for a user with the following in the User model.
public function approvals_pending()
{
return $this->hasMany('App\Approval', 'allowed_user_type', 'user_type')
->where('approved', '=', 0);
}
The question is, how do I combine this with a theoretical filter?
I mean ideally, I would love to write:
return $this->hasMany('App\PO')->whereIn('id', '=', $this->approvals_pending()->get()->po_id);
Or something like that...
Any ideas would be greatly appreciated.
OK, for anyone interested I found a solution:
It's very close to what I thought I would have to write.
The lists method basically creates a single array out of the selected field, so it can be plugged-in directly to a whereIn method like so:
return \App\PO::whereIn('id', $this->approvals_pending()->lists('po_id'));
I don't know if this is the most Eloquent way of doing this but it does work.
Problem in short: I want to set
conditions on an associated model. How
do I do it?
I'm having a problem with the associations in my CakePHP application.
The associations looks like this:
Event has many EventSum belongs to Account has many AccountUser belongs to User
Event has many EventDebt ... (the rest is the same as above)
Event also belongs to User
The application is a private econonmy program in PHP and uses the CakePHP framework.
An Event is a financial event, a purchase, transaction between accounts etc. It only holds information about date, title and user.
An EventSum holds information about an Account and how much to debit or credit (in one column, just positive or negative).
Account holds information about title of the account.
AccountUser holds an id of an Account and a User. This indicates that
So, now I want to fetch Events based on what accounts a User is associated to. How can I do this?
I want to fetch the following info:
Event, together with the EventSum. The Events are fetched from Accounts where the User has access.
Thanks for any help,
/Magnus
It seems like you want to be able to query your Event class with the following conditions:
'Account.user_id =' => $userId
Is my assumption correct?
When doing queries which require conditions on associated models, you can use either the Containable behaviour (comes with CakePHP 1.3) or the 'Linkable' behaviour (which can be found here).
What happens when you try this (be sure to attach the Containable behaviour to your models first):
$condition = array('Account.user_id =' => $userId);
$contain = array('EventSum' => array('Account'), 'EventDebt' => array('Account'));
$result = $this->Event->find('all', compact('condition', 'contain'));
Note that you might experience issues when 'containing' both EventSum and EventDebt if both of their associations to Account use the same alias name.