Rearranging active record elements in Yii - activerecord

I am using a CDbCriteria with its own conditions, with & order clauses. However, the order i want to give to the elements in the array is way too complex to specify in the order clause.
The solution i have in mind consists of obtaining the active records with the defined criteria like this
$theModelsINeed = MyModel::model()->findAll($criteria);
and then rearrange the order from my php code. How can i do this? I mean, i know how to iterate through its elements, but i donĀ“t know if it is possible to actually change them.
I have been looking into this link about populating active records, but it seems quite complicated and maybe someone could have some better advice.
Thanks

There is nothing special about Yii's active records. The find family of methods will return an array of objects, and you can sort this array like any other array in PHP.
If you have complex sort criteria, this means that probably the best tool for this is usort. Since you will be dealing with objects, your user-defined comparison functions will look something like this:
function compare($x, $y)
{
// First sort criterion: $obj->Name
if ($x->Name != $y->Name) {
return $x->Name < $y->Name ? -1 : 1; // this is an ascending sort
}
// Second sort criterion: $obj->Age
if ($x->Age != $y->Age) {
return $x->Age < $y->Age ? 1 : -1; // this is a descending sort
}
// Add more criteria here
return 0; // if we get this far, the items are equal
}

If you do want to get an array as a result, you can use this method for fetching data that supports dbCriteria:
$model = MyModel::model()->myScope();
$model->dbCriteria->condition .= " AND date BETWEEN :d1 AND :d2";
$model->dbCriteria->order = 'field1 ASC, field2 DESC';
$model->dbCriteria->params = array(':d1'=>$d1, ':d2'=>$d2);
$theModelsINeed = $model->getCommandBuilder()
->createFindCommand($model->tableSchema, $model->dbCriteria)
->queryAll();
The above example shows using a defined scope and modifying the condition with named parameters.
If you don't need Active Record, you could also look into Query Builder, but the above method has worked pretty well for me when I want to use AR but need an array for my result.

Related

Sort Collection By id set

I have an array of ids that i want to sort a given column by them in the collection.
For example,
$suppliersOrder = [8,7,5,1,3,4];
$items = Items::get()->sortBy(function($model) use ($suppliersOrder) {
return array_search($model->supplier_id, $suppliersOrder);
})->values();
This acts like ordering items as [1,3,4,5,7,8] instead of the given order. And if I try sortByDesc, likewise [8,7,5,4,3,1] but I couldn't figure out the way to actually sort them as my given array's order.
My ultimate goal is then running $items->groupBy('supplier.name') so I can have my desired order.
What Alexander Villalobos suggested in the comments, I changed my code like this:
$items = Items::get()->sortBy(function($model) use ($suppliersOrder) {
return rsort($model->supplier_id, $suppliersOrder);
});
Indirect modification of overloaded property App\Item::$supplier_id has no effect
$suppliersOrder = [8,7,5,1,3,4];
$items = Items::get()->sortBy(function($row,$key) use ($suppliersOrder) {
return array_search($row->supplier_id, $suppliersOrder);
});
This should give you sorted collection of items by the order you described in $suppliersOrder. As per Laravel docs, the parameters to the callback function include one being the row for the collection and another being the key of that row in the collection.

Laravel Sum a Collection with multiple items

I have a model SettlementEntries that has a relation to a sub table return $this->hasMany('App\Online', 'entry_id');
When trying to fetch one single Entry i'm able to sum my collection like in the example below.
$item = SettlementEntries::find($id);
$item->Online->sum('field'); // returns the correct sum
My problem starts when the $id is an array, so my result contains 2 SettlementEntries.
$items = SettlementEntries::find($ids);
$items->Online->sum('field'); <- returns zero
What is the correct way to retrieve those sums?
Laravel's Collection makes this very easy, by using dot notation. Some examples here.
The simple way (dot notation):
$sum = $items->sum('Online.field');
More explicit (callback):
Or, if you want to be more explicit, e.g. using conditions/filters: provide a Closure:
$sum = $items->sum(function($item) {
return $item->Online->sum('field');
});

Sorting a NotesDocumentCollection based on a date field in SSJS

Using Server side javascript, I need to sort a NotesDcumentCollection based on a field in the collection containing a date when the documents was created or any built in field when the documents was created.
It would be nice if the function could take a sort option parameter so I could put in if I want the result back in ascending or descending order.
the reason I need this is because I use database.getModifiedDocuments() which returns an unsorted notesdocumentcollection. I need to return the documents in descending order.
The following code is a modified snippet from openNTF which returns the collection in ascending order.
function sortColByDateItem(dc:NotesDocumentCollection, iName:String) {
try{
var rl:java.util.Vector = new java.util.Vector();
var tm:java.util.TreeMap = new java.util.TreeMap();
var doc:NotesNotesDocument = dc.getFirstDocument();
while (doc != null) {
tm.put(doc.getItemValueDateTimeArray(iName)[0].toJavaDate(), doc);
doc = dc.getNextDocument(doc);
}
var tCol:java.util.Collection = tm.values();
var tIt:java.util.Iterator = tCol.iterator();
while (tIt.hasNext()) {
rl.add(tIt.next());
}
return rl;
}catch(e){
}
}
When you construct the TreeMap, pass a Comparator to the constructor. This allows you to define custom sorting instead of "natural" sorting, which by default sorts ascending. Alternatively, you can call descendingMap against the TreeMap to return a clone in reverse order.
This is a very expensive methodology if you are dealing with large number of documents. I mostly use NotesViewEntrycollection (always sorted according to the source view) or view navigator.
For large databases, you may use a view, sorted according to the modified date and navigate through entries of that view until the most recent date your code has been executed (which you have to save it somewhere).
For smaller operations, Tim's method is great!

Model records ordering in Spine.js

As I can see in the Spine.js sources the Model.each() function returns Model's records in the order of their IDs. This is completely unreliable in scenarios where ordering is important: long person list etc.
Can you suggest a way to keep original records ordering (in the same order as they've arrived via refresh() or similar functions) ?
P.S.
Things are even worse because by default Spine.js internally uses new GUIDs as IDs. So records order is completely random which unacceptable.
EDIT:
Seems that in last commit https://github.com/maccman/spine/commit/116b722dd8ea9912b9906db6b70da7948c16948a
they made it possible, but I have not tested it myself because I switched from Spine to Knockout.
Bumped into the same problem learning spine.js. I'm using pure JS, so i was neglecting the the contact example http://spinejs.com/docs/example_contacts which helped out on this one. As a matter of fact, you can't really keep the ordering from the server this way, but you can do your own ordering with javascript.
Notice that i'm using the Element Pattern here. (http://spinejs.com/docs/controller_patterns)
First you set the function which is gonna do the sorting inside the model:
/*Extending the Student Model*/
Student.extend({
nameSort: function(a,b) {
if ((a.name || a.email) > (b.name || b.email))
return 1;
else
return -1
}
});
Then, in the students controller you set the elements using the sort:
/*Controller that manages the students*/
var Students = Spine.Controller.sub({
/*code ommited for simplicity*/
addOne: function(student){
var item = new StudentItem({item: student});
this.append(item.render());
},
addAll: function(){
var sortedByName = Student.all().sort(Student.nameSort);
var _self = this;
$.each(sortedByName, function(){_self.addOne(this)});
},
});
And that's it.

Filtering Aggregate root entity and child entity by a property on the child entity

Hope that someone out there can help with this!
I'll give an example based on the standard Order-->OrderLine-->Product rather than the actual situation to make it easier to explain!
Basically, I want to run a query that returns all orders for which there is an order line containing a TV. Simple enough:
IEnumerable<Order> orders;
using (var context = new DataContext())
{
var source =
context.Orders.Include("OrderLines").Include(
"OrderLines.Product");
orders= source.Where(o => o.OrderLines.Where(ol => ol.Product.Name == "TV")).ToList();
}
return orders;
This works in the sense that I get the correct collection of Order entities, but when I use look at each Order's collection of OrderLines it contains all OrderLines not just those containing at TV.
Hope that makes sense.
Thanks in advance for any help.
I does make sense in that the query is fulfilling your original criteria "to return all orders for which there is an order line containing a TV", each order will of course have all the orderlines. The filter is only being used to select the Orders, not the OrderLines.
To retrieve just the OrderLines containing TV from an Order you'd use the filter again, thus:
var OrderLinesWithTV = order.OrderLines.Where(ol => ol.Product.Name == "TV");
The main point is to know if you need to keep (or not) a reference to the order header in the filtered lines.
I.e. do you want the list of all the orders with a TV, and more precisely only their TV lines ? or do you want all the TV lines nevermind their order header ?
You seem to prefer the first option.
Then the best solution would certainly be
var relevantOrders = orders.Where(order => order.OrderLines.Any(ol => ol.Product.Name == "TV"))
to get the relevant orders, and then, for each order in relevantOrders :
order.OrderLines.Where(ol => ol.Product.Name == "TV")
to consider only the TV lines.
Other techniques would result in a loss of information or force you to build a new orders collection similar to the initial one but double-filtered on the headers and on the lines, which seems fairly bad as far as elegance and performance is concerned.

Resources