Laravel - Shuffle Merge and Paginate Collection - laravel

I am struggling with a pretty difficult thing and hope you can help me out.
Right now I've got the following:
$ads = Ad::where('status', 1)
->whereIn('ad_type', [1, 2, 3])
->where('expire_at', '>', date('Y-m-d H:i:s'))
->where('special_ad', 'standard_ad')
->orderByRaw(DB::raw("FIELD(ad_type,2,3,1)"));
Info:
This is working because it is an Eloquent Collection and I can Paginate this (needed for my Infinite Scroll)
But now I want to shuffle the ad_types in itself, meaning:
ad_type 1 could have, let's say, 30 entries. They will all be returned in the usual order. I want to shuffle those 30 every time I run this query.
I thought about doing ->toArray(); but then again, no pagination (Pagination only works on Eloquent Queries right?)
Then I thought, hey, let's merge this.
But as soon as I did that, the returned collection is no longer an Eloquent Collection but a Support Collection (right? I am not 100% sure it is a Support Collection) thus the Pagination does not work anymore.
I read upon many posts as how to solve this problem, and figured out one solution may be to "create my own paginator instance"
But heck, I am not that good yet. I do really not know, even after studying the laravel documentation, how to create my own paginator.
Important Infos you might need:
Using Laravel 5.2
$ads are dynamical, meaning depending on the case, the requests sent with Ajax, the query might differ at a later point (something might get included).
Thank you very much for your time reading this and hopefully, you can help me and future readers by solving this particular problem.
Greetings, and a great weekend to all of you.

Firstly just to note:
Pagination does not only work for database queries. You can manually paginate using LengthAwarePaginator but manually is the keyword here. The query builder (not Eloquent) can do it automatically for you using paginate.
You can "shuffle" the results by doing something like
$ads = Ad::where('status', 1)
->whereIn('ad_type', [1, 2, 3])
->where('expire_at', '>', date('Y-m-d H:i:s'))
->where('special_ad', 'standard_ad')
->orderByRaw("FIELD(ad_type,2,3,1)")
->orderByRaw("RAND()");
This will order by the ad_type field first and order by a random number (different for every row) as a secondary sort.

Related

How to check if user is sorting table's column in datatables

I'm using yajra's datatables on Laravel 8, trying to implement "default sorting" on the server-side. It's already working, but I'm trying to find other solutions because the implementation feels a bit hacky.
I have something like this in my controller:
$myData = new MyData;
if ($request->order[0]['column'] === '0') {
$myData->orderBy('priority');
}
return DataTables::of($myData)->make(true);
Please notice the if ($request->order[0]['column'] === '0') part. The idea here is that if the table is initiated for the first time, alias users haven't made any column sorting by themselves, then the data should be sorted by whatever is defined inside the if-statement. The datatables itself doesn't have a defined order property, thus the sorting is given to column 0 by default.
I'm doing the sorting from the server-side because it's a bit complicated and will be easier to be done from the server rather than datatables. Once again, the code above IS working, but I just feel that this is a bit hacky. Is there any other alternatives I can use? Should I change the code, or is this alright?
So in your blade code on the front add this to your function to fill the datatable...
order: [],
This will tell datatables to not do any sorting of the data and use what you give it as the first sort order. Basically giving it an empty array.
Then you can get rid of the condition totally. Then just sort your data like you normally would in your eloquent collection you are making to fill said datatable.
Here is the documentation for this:
https://datatables.net/reference/option/order

Eloquent ORM get latest items query

I am trying to do get all the latest items and sort by id descending (i.e. get all items that were just added with a limit and offset).
So I did this:
$products = Product::all()
->slice($request->get('offset'))
->take($request->get('limit'))
->sortByDesc('id')
->toBase();
However it seems when I have more that that limit, then I dont have the right order. It gets me say 10 products but not sorted corrected. Any idea how to do this with Eloquent ORM?
You are probably intending to have the database handle the offset and skipping and ordering instead of pulling all the possible records then taking only what you want, then sorting them ... if you were going to do it your way you would need to sort before you skip and take, by the way.
Using the database to the the filtering and ordering:
$products = Product::skip($request->input('offset'))
->take($request->input('limit'))
->orderBy('id', 'desc')
->get();
I think the issue is you're using ::all() first, which returns all Product instances in a Collection, then using collection methods. Since these methods act in order of usage, you're slicing and offsetting before sorting, so you'll get the same products each time. Use proper Builder syntax to handle this properly and more efficiently:
$products = Product::offset($request->input("offset"))
->limit($request->input("limit"))
->orderBy("id", "DESC")
->get();
Because this is a Builder instance, the query will be compiled and executed according to your Database's grammar logic, and in a single query. There's nothing wrong in using Collection logic, you'd simply have to use the correct order of methods (sortByDesc() first, then slice(), then take()), but this is incredibly inefficient as you have to handle every Product in your database.

Elquent Relation Limit records

I'm trying to send API with certain number of records
$category->products
I tried to use limit() or take() but it fails
so is there any smart solution than go to pivot and select it with limit ??
Copy data to a new collection.
$collection =$category->products;
you can now send the number of times you want using take ().
for example;
$collection->take(5);
if your question is different please explain it more clearly.
I found the solution I was easy
$category->products()->limit(2)->get()->all()
that's all

Eloquent deleting more records than intended?

Post::whereReplyTo($request->input('reply_to'))
->orderBy('updated_at', 'desc')
->offset(Config::PAGE_SIZE * Config::MAX_PAGES)
->take(1024)
->delete();
I intend to fire this when post count reaches 4 in order to maintain a maximum of 4 posts, in this case.
Problem is it deletes ALL posts, not only those that I intended to delete
I'm becoming frustrated, why is this happening? theres no error, and I toSql'd the query and nothing is wrong, the selction part is correct, I tried it, so why is it deleting ALL posts???
Well, if your research is correct, you should still be able to do it with two steps:
$delete_posts = Post::select('id')->whereReplyTo($request->input('reply_to'))
->orderBy('updated_at', 'desc')
->offset(Config::PAGE_SIZE * Config::MAX_PAGES)
->take(1024)
->get()->toArray();
Post::whereIn('id', $delete_posts)->delete();
I think I'm on to something
I appended a listener to log the last query after running my Delete
the result was:
delete from `a2_posts` where `reply_to` = ? order by `updated_at` desc limit 1024
so where is my offset? I googled around and I think you cant use offset + delete.
retarded? yes, very much so.
This is why I like Mongo better.
It makes more sense.
Source: Mysql delete statement with limit
so what I think I'll do is query, then iterate, then delete.
SQL is pure genius sometimes I swear to God.

how to chop a DataMapper Collection into one Collection per day?

I have a DataMapper::Collection Object. Each of it's entries has a created_at property. I want to render the entries into html tables, one table per day (I use Sinatra for that).
It was no problem to render everything into one table, but I didn't get it to do so for every day. I thought of an array of DataMapper::Collection objects over which I would just iterate and do the job. But I don't know how to build such an array :/
Does anyone know how to solve my problem, or does anyone have a different/better approach?
Thanks in advance!
You have (at least) two options. The first is to let the database do the work for you. I don't know about datamapper but most database mappers (!) have functionality to group using SQL's GROUP BY. In this case you would have to use a database function to get the date out of the timestamp and then group on that. This is the fastest option and if you and future maintainers are familiar with relational databases probably also the best.
The second option is to to the mapping in your code. I can't come up with an elegant Ruby thing right now but you could at least do:
mapped_result = Hash.new [] # initiates each new entry with empty array
mapper_collection.each do |one_record|
mapped_result[one_record.created_at.strftime '%Y-%m-%d'] << one_record
end
and then you can get to record for a day with
mapped_result['2012-11-19']

Resources