Good day,
I'm a bit stuck here with fetching latest item using Laravel scopes and Eloquent Polymorphic One-to-Many relationship.
Given:
I'm using latest version of Laravel 6.x.
I have two models: Website and Status.
Status model is reusable and can be used with other models.
Each website has multiple statuses.
Every time status is changed a new record is created in DB.
Active website status is the latest one in the DB.
Websites model:
class Website extends Model
{
public function statuses()
{
return $this->morphMany(Statuses::class, 'stateable');
}
public function scopePending($query)
{
return $query->whereHas('statuses', function ($query) {
$query->getByStatusCode('pending');
});
}
}
Statuses model:
class Statuses extends Model
{
public function stateable()
{
return $this->morphTo();
}
public function scopeGetByStatusCode($query, $statusCode)
{
return $query->where('status_code', $statusCode)->latest()->limit(1);
}
}
The problem is that when I call:
Website::pending()->get();
The pending() scope will return all websites that have ever got a pending status assigned to them, and not the websites that have currently active pending status (eg. latest status).
Here is the query that is returned with DB::getQueryLog()
select * from `websites`
where exists
(
select * from `statuses`
where `websites`.`id` = `statuses`.`stateable_id`
and `statuses`.`stateable_type` = "App\\Models\\Website"
and `status_code` = 'pending'
order by `created_at` desc limit 1
)
and `websites`.`deleted_at` is null
What is the right way of obtaining pending websites using scope with polymorphic one-to-many relation?
Similar issue is descried here: https://laracasts.com/discuss/channels/eloquent/polymorphic-relations-and-scope
Thanks.
Okay, after doing some research, I have stumbled across this article: https://nullthoughts.com/development/2019/10/08/dynamic-scope-on-latest-relationship-in-laravel/
Solution turned out to be quite eloquent (sorry for bad pun):
protected function scopePending($query)
{
return $query->whereHas('statuses', function ($query) {
$query->where('id', function ($sub) {
$sub->from('statuses')
->selectRaw('max(id)')
->whereColumn('statuses.stateable_id', 'websites.id');
})->where('status_code', 'pending');
});
}
Related
Am not a pro in neither PHP nor Laravel and I think I am cornered. In my assessment app, I have courses, lessons and assessment tests. An assessment_test has many to one relationship with lesson. lesson also has many to one relationship with course.
Now here is the problem, How do I retrieve all courses to which a collection of assessment_tests belong?
As shown in the code snippets provides, I tried to get around this by manually looping through the collection and saving the courses to an array but I got some weird error.
Trying to get property 'course' of non-object
Here is my problematic function
public function index()
{
$attempts=AssessmentAttempt::all();
$i=0;
$courses=array();
foreach ($attempts as $attempt) {
// dd($attempt->lesson->course);
$courses[$i++]=$attempt->lesson->course;
}
dd(array_unique($courses));
return view('achievements.index', ['attempts'=>$attempts, 'courses'=>$courses]);
}
AssessmentAttempt model
public function lesson()
{
return $this->belongsTo(Lesson::class);
}
Lesson model
public function course()
{
return $this->belongsTo(Course::class);
}
public function assessmentAttempts()
{
return $this->hasMany(AssessmentAttempt::class);
}
in the assesment_modal define the relation ship with course and make the course id column as a foreign_key in the assesment_model table
return $this->hasMany(Course::class, 'foreign_key');
also design the relation in course model
return $this->belongsTo(Assesment_Model::class);
So I have three models: Volunteer, Task and Payment. A Volunteer can have many (has many relationship) tasks and a task can have many (another has many relationship) payments.
class Volunteer
public function tasks()
{
return $this->hasMany(Task::class);
}
class Task
public function volunteer()
{
return $this->belongsTo(Volunteer::class);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
class Payment
public function task() {
return $this->belongsTo(Task::class);
}
Now I want to query all volunteers with unpaid/ partially paid tasks. So, basically I want to filter a volunteer's tasks where each task's amount should equal the sum of all payments linked to that particular task.
I tried using whereHas and with but I don't seem to be able to filter the tasks properly.
I've managed to do it with joins but was wondering if it's possible to with whereHas or with. Below is the code:
Volunteer::select('volunteers.id', 'volunteers.name', 'tasks.amount', DB::raw('SUM(payments.amount) as amount_paid'))
->join('tasks', 'tasks.volunteer_id', '=', 'volunteers.id')
->leftJoin('payments', 'payments.task_id', '=', 'tasks.id')
->groupBy('volunteers.id', 'volunteers.name', 'tasks.amount')
->havingRaw('amount_paid >= tasks.amount')
->get();
Any help would be appreciated!
I would like to suggest something else which is adding a column in tasks table that indicates if the task is [paid, unpaid or partially paid] in your tasks migration like so
$table->unsignedTinyInteger('paid_status')->default(0); // 0 -> unpaid, 1 -> partially paid, 2 -> paid
then each time the volunteer makes a payments you will do a simple check to update tasks.paid_status something like checking the total paid_amount and task amount
then using Laravel hasManyThrough in Volunteer model like so
public function payments()
{
return $this->hasManyThrough(
'App\Payment',
'App\Task'
);
}
now to get your data you will do so
// unpaid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '0')->get();
// partially paid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '1')->get();
// paid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '2')->get();
you can read more about HasManyThrough Here
You can handle this using the eloquent powers as well. Extends the Task model with a local scope method
class Task extends Model
{
public function volunteer()
{
return $this->belongsTo(Volunteer::class);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
public function scopeIncompletePayments($query)
{
return $query->select('tasks.*')->leftJoin('payments', 'tasks.id', '=', 'payments.task_id')
->having('tasks.amount', '>', DB::raw('SUM(payments.amount)'))
->groupBy('tasks.id')
->with(['volunteer', 'payments']);
}
}
Which allows you to run the following code to get the tasks where the sum of the related payments is less than the amount of the task. With the payments and Volunteer eagerly loaded
Task::incompletePayments()->get()
I am trying to understand how to effectively use Eloquent relationships to have some high level functions in the model.
I have a subscription app with 2 tables, 'users' and 'subscriptions'.
This is a legacy system so I cannot just change things in any way I want.
Table users (model App\User)
id
email
active (0/1)
join_date
address etc
phone
Table subscriptions (model App\Subscription)
id
user_id
box_id (what the person is subscribed to get)
amount
Users are marked active or not active.
I would like to have a static method on the Subscription model that will give me all the active subscriptions. This data is then fed into other parts of the application.
This is derived by joining subscriptions to users and filtering based on the active column.
The query is like this:
SELECT users.*, subscriptions.*
FROM subscriptions
JOIN users ON users.id = subscriptions.user_id
WHERE users.active = 1
Subscription model
class Subscription extends Model
{
public static function allActive()
{
// This works except it doesn't use the eloquent relationship
return static::where('users.active', 1)
->join('users', 'users.id', '=', 'subscriptions.user_id')
->select('users.*','subscriptions.*')
->get();
}
public function user()
{
return $this->belongsTo(User::class);
}
}
User model
class User extends Authenticatable
{
use Notifiable;
public function subscriptions()
{
return $this->hasMany(Subscription::class);
}
}
I would use it like this:
$subscriptions = \App\Subscription::allActive()->toArray();
print_r($subscriptions);
I have 2 questions.
How do I rewrite the allActive function to use the relationship I already defined? Any solution should generate SQL with a JOIN.
In the returned data, how do I separate the columns from the two separate tables so that it is clear which table the data came from?
Given the relationships you have wired up, to get only active subscriptions from the model class you will have to do it this way:
class Subscription extends Model
{
public static function allActive()
{
$activeSubcriptions = Subscription::whereHas('user', function($query){
$query->where('active', 1) //or you could use true in place of 1
})->get();
return $activeSubcriptions;
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Thats working with closures in Laravel, quite an efficient way of writing advanced eloquent queries.
In the callback function you will do pretty much anything with the $query object, its basically working on the User model since you mentioned it as the first parameter of the ->whereHas
Note that that variable has to have EXACTLY the same name used in declaring the relationship
The above i suppose answers your first question, however its highly recommended that you do most of this logic in a controller file
To answer question 2, when you execute that get() it will return Subscription objects array so to access the info based on columns you will have to go like:
$subscriptions = \App\Subscription::allActive();
foreach($subscriptions as $subscription){
$amount = $subscription->amount; //this you access directly since we working with the subscription object
$box_id = $subscription->box_id;
//when accessing user columns
$email = $subscription->user->email; //you will have to access it via the relationship you created
$address = $subscription->user->address;
}
I have a one to many relationships in the database, and I am trying to extract the latest. The table names are notification and alertFrequency, they have models for each. I want to write a query that will get me the latest time stamp associated to a specific website in the notifications table. The alertFequency table has only 2 columns namely notification_id and created_at. the following are my model notification modl
class Notification extends Model
{
protected $fillable = ['id','website_url','email','slack_channel','check_frequency','alert_frequency','speed_frequency','active'];
public function statuses(){
return $this->belongsToMany('App\Status')->withPivot('values')->withTimestamps();
}
public function alertFrequencies(){
return $this->hasMany('App\AlertFrequency');
}
// trail versions
public function alert(){
$items = AlertFrequency::select('alertFrequencies.created_at')//Any column you want to fetch
->join('notifications', 'notifications.id', '=', 'alertFrequencies.notification_id')
->orderBy('alertFrequencies.created_at','desc')
->first();
if($items == null){
return null;
}
return $items->created_at->toDateTimeString();
class AlertFrequency extends Model{
protected $table = 'alertFrequencies';
public function notification(){
return $this->belongsTo('App\Notification');
}
}
inside the notification model i wrote a function that is expected to extract the data(i.e the latest time stamp of a specific website in the notification table) more over the notification_id is a foreign key in the alertFrequency table. the alert function is as follows
public function alert(){
$alert_timestamp = AlertFrequency::with('notification')->select('created_at')->orderBy('created_at','desc')->first();
//$alert_timestamp=$alert_timestamp->created_at->toDateTimeString();
if($alert_timestamp==null){
return false;
}
return $alert_timestamp;
}
it is returnning created_at time stamp but not the latset related to a specific website. i would apperciate your help?
in the database i have two websites added one at 12:18 and the second ata 12:24..... sorry i dont know how i can post the database here.
Directly You can not apply orderBy() on relation you need to use join()
Just try this. Hope it helps
$alert_timestamp = AlertFrequency::select('alertFrequencies.created_at')//Any column you want to fetch
->join('notification', 'notification.notification_id', '=', 'alertFrequencies.notification_id')
->orderBy('alertFrequencies.created_at','desc')
->get();
I have two Models called Channel and AppUser which are related through a pivot table called app_user_channels.
Users are able to "follow" many channels, and channels can have many users so I defined my relationship as follows:
class AppUser extends Model {
protected $fillable = ['country'];
public function channels () {
return $this->belongsToMany('App\Channel', 'app_user_channels');
}
}
class Channel extends Model {
public function appUser () {
return $this->belongsToMany('App\AppUser', 'app_user_channels');
}
}
class AppUserChannel extends Model {
public function appUser() {
return $this->belongsTo('App\AppUser');
}
public function channel() {
return $this->belongsTo('App\Channel');
}
}
I need to get the top five most recurring countries amongst the Channel's AppUsers. Now, I know that I can get the Channel's followers from the Channel Model by doing something like return $this->appUser->groupBy('country'), but how can I get the country and count for the most common countries among the Channel's followers (AKA AppUsers)?
I'm using Laravel 5.3 and have read through the Eloquent documentation, but am still unable to figure it out. Any hints would be highly appreciated.
Try the following, the code hasn't been tested, but hopwfully, it should work. Don't forget to import DB.
return $this->appUser
->select('country', DB::raw('count(*) as total'))
->groupBy('country')
->orderBy('total', 'desc')
->get()
->take(5)
->pluck('country','total');