Laravel sort and save position in database - php

I have a table in my database called 'users', it has these columns:
id, points, rank.
Everytime I update points, I want to sort all users by points desc and save rank position in that column.
I was wondering about getting all rows from database, sort them by using collection method and using update to update rank column. But i was wondering about how intense it would be to update 100k users one by one.
Maybe some experienced Laravel developers can suggest me a best possible solution in this situation?

If you want data orderred by points you can use
$user = User::orderBy('point', 'desc')
->get();

Alright the things you need here is an Eloquent Observer so each time you update the points on update it will sort all the users by points desc and save the rank position.
However with that amout of data I would never use collections or anything simillar as it will be slow, I suggest you using raw methods to do that.
Now there are two possible options if latency matters on this case then you can place all the logic in the observer itself like below:
First of seperate the observer logic to a different class like:
namespace App\Observers;
class UserRankObserver
{
public function updated($model)
{
//check if points column its updated
//if yes then run the query to sort all user by points and save rank
}
}
Then on your User model you need to register the observer like :
public static function boot()
{
parent::boot();
User::observe(new \App\Observers\UserRankObserver);
}
Case two when the latency does not matter then I suggest on the method updated I would just pass the data to a background job to complete the action like :
namespace App\Observers;
class UserRankObserver
{
public function updated($model)
{
//check if points column its updated
//if yes then pass the data to a background job to complete the task.
(new UpdateUserRankJob($data))->dispatch();
}
}

Related

Laravel: Order by column value count?

I used to have a query like this
$topReferrals = User::orderBy('user_referrals', 'DESC')->get();
Recently I changed my database structure to not count user_referrals for each user in an int datatype, but to have a column for each user called referred_by and have its value who they have been referred by, I need to adapt my query to work with the new system.
I'm not quite sure how I would go about this, I was hoping someone could help?
Eloquent offers several means of counting relations. One approach would be to set the $withCount property on the User model:
// assuming your model has a referrals method.
protected $withCount = ['referrals'];
This will append an attribute on each queried model that can be used in subsequent queries or collection modifications.
User::all()->sortByDesc('referrals');
Just add referred_by column in User table as it denotes foreign key linked to the user.
Use query below to get count of users that have been referred for each referrer:
User::where('referred_by', $referred_user_id)->count();
OR
User::where('referred_by', $referred_user_id)->orderByDesc('referred_by')->get();
Note: Just make sure you have created a table for referrers to store all the details for referrers in case you want to grabs other data regarding them. You just need to add relations to the User model to directly access to the database.
public function hasReferrers () {
$this->hasOne('App\Referrer');
}
Where App\Referrer is model for your referrers table.
As I’ve understood the question, I think this is how I’d approach getting the number of referrals per user..
$all = User::all();
foreach ($all as $current) {
$current->referrals = User::where('referred_by', $current)->count();
}
$all->sortBy('referrals');
$all should now be a list of users sorted by the number of referrals.

Laravel 5 - Conditionally append variables

A few words before
I know that you can append variables to model arrays and json representations by using the protected $appends = ["your", "vars", "here"]; array. But imagine the following situation:
The situation
Our use case would be a fictional game or similiar:
Imagine that we have a User model that holds simple information about an (human) user, like the full name, address and so on.
Now, we also have a Faction model that represents the faction/origin/guild/... of this user.
The Faction model is eager-loaded when retrieving users, because the Faction name is wanted almost every time when displaying the user information.
A User also has DailyStatistics, which holds some information about their daily scores (simple points would be enough).
The Clue
Because I want to know the points of the a faction, which is the sum of the user points, I thought about appending a new variable totalPoints.
The getTotalPointsAttribute function would look like this:
function getTotalPointsAttribute(){
return $this->users->sum->getTotalPoints();
}
The problem
Everytime when we retrieve a user now, the eager-loaded faction would also want to calculate the totalPoints attribute. That means, that we have a lot of overhead per user.
The question
Is there a way to avoid situations like this? Can I "conditionally" append variables? Are properties calculated when they are hidden?
I tried to wrap the totalPoints variable in a simple function, instead of an accessor instead. The problem is, that Frontend-Frameworks like VueJS would need access to the totalPoints variable (or to an endpoint to retrieve that value, but this solution is the least favorable).
I met this problem as I wanted to Appends on the fly but don't want this to auto-appends on any other Controller/Models (The other way is produce 2 Models for the same Table, which is difficult to maintain).
Currently I'm maintaining a Laravel 5.4 (Since they refuse to upgrade PHP5.6 to PHP7)
For Laravel 5.4 and below
Just add a closure after completed the query builder get()
->each(function ($items) {
$items->append('TotalPoints');
);
Source of original solutions: laravel-how-to-ignore-an-accessor
$openOrders = Order::open()->has('contents')
->get(['id','date','tableName'])
->each(function ($items) {
$items->append('TotalPoints');
);
Your model still contains the
public function getTotalPointsAttribute()
{
return $this->users->sum->getTotalPoints();
}
Now you can remove/comment out the the appends in your models :
protected $appends = [
'TotalPoints',
];
Alternatively, if you're on Laravel 5.5 and above, you could use the collection magic like so:
$openOrders->each->setAppends(['TotalPoints']);
Laravel 5.5 and above now have a Laravel 5.6 #Appending At Run Time

Concept of table relations in Laravel

I have two tables:
Cards
Notes
Each Card has multiple Notes. So there is a relation between them like this:
class Card extends Model {
public function notes ()
{
return $this->hasMany(Note::class);
}
}
Ok well, all fine.
Now I need to understand the concept of these two lines:
$card()->$notes()->first();
and
$card()->$notes->first();
What's the difference between them? As you see in the first one $note() is a function and in the second one $note isn't a function. How will they be translated in PHP?
The first one points out to the card table and the second one points out to the notes table, right? or what? Anyway I've stuck to understand the concept of tham.
I don't know about $ before the $notes in your code but if you trying to say something like this.
1- $card->notes()->first();
2- $card->notes->first();
In the code in line 1, first you have a $card model and then you wanted to access all notes() related to that $card, and because of adding () after notes you simply call query builder on notes, show you can perform any other database query function after that, something like where, orderBy, groupBy, ... and any other complicated query on database.
But in the second one you actually get access to a collection of notes related to that $card, we can say that you get all related notes from database and set it into laravel collections and you are no more able to perform database query on notes.
Note: because laravel collections have some methods like where(), groupBy(), whereIn(), sort(), ... you can use them on the second one, but in that case you perform those methods on collections and not database, you already get all results from database

Populate a Select Box with Eagerloading

Any idea what the best way would be to populate a select box with eagerloading?
For example: I can eagerload a hasOne relation like so which works fine for showing the initial status.
Order Model
public function status()
{
return $this->hasOne('OrderStatus','id','status');
}
But then on the order page I also need to populate a select box with all statuses so a user can change the order status.
Order Status Model
public function allStatuses()
{
return OrderStatuses::all();
}
and then I try calling with the dot notation, but that does not work
$order::with('orderStatus','orderStatus.allStatuses')->whereId(1000)->get();
Basically, my views contain a lot of drop-down for things like country, state, etc. I know I can send them through the view with multiple variables, but I would like to just call everything in one collection to the view if possible.
I know the above is probably wrong, but any suggestion on the best way to do this would be appreciated.
Right now I am calling all my common selects from a service provider and then import my repos into that so I can call directly from my view like FormList::getStates(), FormList::getCountries(), etc.. Not sure if this is the best way.

Laravel, do I need multiple database tables for votes on different models

I have a Question model which has a one to many relationship with an Answer model.
Now I want to add upvote/downvote funcionality to both of these models, do I need to create two tables like VotesQuestions and VotesAnswers or can I somehow manage with one? If so, how?
You can use a polymorphic relationship. This is built into Laravel. Documentation is here. The code shown here is for Laravel 4, but the functionality is the same for Laravel 5.
Create a votes table, and make sure it has at least two specific fields: votable_id and votable_type. In a database migration, you would use the statement $table->morphs('votable');, and it will create the two fields. You can have as many other fields as you like, but to make sure the relationship works, those two fields are required.
Next, setup the Vote model with the votable relationship. The name of this relationship should match the base name of the fields you created:
class Vote extends Eloquent {
public function votable() {
return $this->morphTo();
}
}
With this setup, you can now associate votes to any model you want. Go ahead and add the votes relationship to the Question and Answer models:
class Question extends Eloquent {
public function votes() {
return $this->morphMany('Vote', 'votable');
}
}
class Answer extends Eloquent {
public function votes() {
return $this->morphMany('Vote', 'votable');
}
}
You can now access the votes for any question/answer through the relationship:
$q = Question::first();
$qVotes = $q->votes; // Collection of votes for the question.
$a = Answer::first();
$aVotes = $a->votes; // Collection of votes for the answer.
You can also get the related question/answer model through the vote, if you ever need to:
$v = Vote::first();
$vRelated = $v->votable; // Will automatically be a Question or Answer object, depending on what the vote was for.
I would do an table for the question and when you want to up/downvote the question there should be a count column for both, otherwise you want to log it that an user can only vote for it once, so you need another table for user_id, question_id and type (up/down).
ofc you can handle it with one table, but that is really worth because you save many things that are not necessary.
you can create a table with an internal id, 1,2,3,4 and 1 is always the question or 0 and 2-xx (1-xxx) are always the answers. so you can handle it with one table
You could create a generic Votes model/table which has a field called "model" and "model_id" and then use reflection to get the correct object.

Categories