Files
TinyORM/docs/tinyorm/collections.mdx
silverqx 978f9160fb docs bugfix class vs className 😱
[skip ci]
2024-08-16 20:40:22 +02:00

1346 lines
40 KiB
Plaintext

---
sidebar_position: 2
sidebar_label: Collections
description: The ModelsCollection is specialized container which provides a fluent, convenient wrapper for working with vector of models. Is much more powerful than vectors and expose a variety of map / reduce operations that may be chained using an intuitive interface. All TinyORM methods that return more than one model result will return instances of the ModelsCollection class.
keywords: [c++ orm, orm, collections, collection, model, tinyorm]
---
import Link from '@docusaurus/Link'
# TinyORM: Collections
- [Introduction](#introduction)
- [Creating Collections](#creating-collections)
- [Available Methods](#available-methods)
## Introduction
<div className="api-stability alert alert--success">
<Link to='/stability#stability-indexes'>__Stability: 2__</Link> - Stable
</div>
The `Orm::Tiny::Types::ModelsCollection` is specialized container which provides a fluent, convenient wrapper for working with vector of models. All TinyORM methods that return more than one model result, will return instances of the `ModelsCollection` class, including results retrieved via the `get` method or methods that return relationships like the `getRelation` and `getRelationValue`.
The `ModelsCollection` class extends `QList<Model>`, so it naturally inherits dozens of methods used to work with the underlying vector of TinyORM models. Be sure to review the [`QList`](https://doc.qt.io/qt/qlist.html) documentation to learn all about these helpful methods!
:::info
The `ModelsCollection` template parameter can be declared only with the model type or a model type pointer, it also can't be `const` and can't be a reference. It's constrained using the `DerivedCollectionModel` concept.
:::
You can iterate over the `ModelsCollection` the same way as over the `QList`:
```cpp
using Models::User;
ModelsCollection<User> users = User::whereEq("active", true)->get();
for (const auto &user : users)
qDebug() << user.getAttribute<QString>("name");
```
However, as previously mentioned, collections are much more powerful than vectors and expose a variety of map / reduce operations that may be chained using an intuitive interface. For example, we may remove all active users and then gather the first name of each remaining user:
```cpp
auto names = User::all().reject([](User *const user)
{
return user->getAttribute<bool>("active");
})
.pluck("name");
```
As you can see, the `ModelsCollection` class allows you to chain its methods to perform fluent mapping and reducing of the underlying vector. In general, collections are immutable, meaning every `ModelsCollection` method returns an entirely new `ModelsCollection` instance.
:::info
The `ModelsCollection<Model>` is returning from the Models' methods like `get`, `all`, `findMany`, `chunk`; the `ModelsCollection<Model *>` is returning from the relationship-related methods as `getRelation` and `getRelationValue`.
:::
#### Collection Conversion
While most TinyORM collection methods return a new instance of `ModelsCollection`, the `modelKeys`, `mapWithKeys`, and `pluck` methods return a base QList or std unordered/map instances. Likewise, one of the `map` methods overload returns the `QList<T>`.
### Creating Collections
Creating a `ModelsCollection` is as simple as:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
};
```
You can also create a collection of pointers, eg. `ModelsCollection<User *>`:
```cpp
ModelsCollection<User *> userPointers {
&users[0], &users[1],
};
```
The `ModelsCollection<Model>` is implicitly convertible and assignable from the `QList<Model>`:
```cpp
QList<User> usersVector {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
};
ModelsCollection<User> users(usersVector);
users = usersVector;
```
Alternatively, you can use the `Orm::collect<Model>` helper function to create a `ModelsCollection` from the given attributes:
```cpp
ModelsCollection<User> users = Orm::collect<User>({
{{"name", "Kate"}, {"added_on", QDateTime::currentDateTimeUtc()}},
{{"name", "John"}, {"added_on", QDateTime({2023, 6, 1}, {13, 46, 15}, QTimeZone::UTC)}},
});
```
:::caution
The `Orm::collect<Model>` function is __mandatory__ if your attributes contain the `QDateTime` instance, you can read more about this problem [here](tinyorm/getting-started.mdx#qdatetime-and-connection-name-problem).
:::
:::note
The results of [TinyORM](tinyorm/getting-started.mdx) queries are always returned as `ModelsCollection` instances.
:::
## Available Methods
For the majority of the remaining collection documentation, we'll discuss each method available on the `ModelsCollection` class. Remember, all of these methods may be chained to fluently manipulate the underlying vector.
Furthermore, almost every method returns a new `ModelsCollection` instance, allowing you to preserve the original copy of the collection when necessary:
<div className="collection-methods-list">
[all](#method-all)
[contains](#method-contains)
[doesntContain](#method-doesntcontain)
[each](#method-each)
[except](#method-except)
[filter](#method-filter)
[find](#method-find)
[first](#method-first)
[firstWhere](#method-first-where)
[fresh](#method-fresh)
[implode](#method-implode)
[isEmpty](#method-isempty)
[isNotEmpty](#method-isnotempty)
[last](#method-last)
[load](#method-load)
[map](#method-map)
[mapWithKeys](#method-mapwithkeys)
[mapWithModelKeys](#method-mapwithmodelkeys)
[modelKeys](#method-modelkeys)
[only](#method-only)
[pluck](#method-pluck)
[reject](#method-reject)
[sort](#method-sort)
[sortBy](#method-sortby)
[sortByDesc](#method-sortbydesc)
[sortDesc](#method-sortdesc)
[stableSort](#method-stablesort)
[stableSortBy](#method-stablesortby)
[stableSortByDesc](#method-stablesortbydesc)
[stableSortDesc](#method-stablesortdesc)
[tap](#method-tap)
[toBase](#method-tobase)
[toJson](#method-tojson)
[toJsonArray](#method-tojsonarray)
[toJsonDocument](#method-tojsondocument)
[toMap](#method-tomap)
[toMapVariantList](#method-tomapvariantlist)
[toQuery](#method-toquery)
[toList](#method-tolist)
[toListVariantList](#method-tolistvariantlist)
[unique](#method-unique)
[uniqueBy](#method-uniqueby)
[uniqueRelaxed](#method-uniquerelaxed)
[uniqueRelaxedBy](#method-uniquerelaxedby)
[value](#method-value)
[where](#method-where)
[whereBetween](#method-wherebetween)
[whereIn](#method-wherein)
[whereNotBetween](#method-wherenotbetween)
[whereNotIn](#method-wherenotin)
[whereNotNull](#method-wherenotnull)
[whereNull](#method-wherenull)
</div>
:::note
For a better understanding of the following examples, many of the variable declarations below use actual types instead of the `auto` keyword.
:::
<div className='collection-methods'>
#### `all()` {#method-all}
The `all` method returns a copy of the underlying vector represented by the collection:
```cpp
QList<User> = users.all();
```
:::note
The [`toBase`](#method-tobase) is an alias to the `all` method.
:::
#### `contains()` {#method-contains}
The `contains` method may be used to determine if a given model instance is contained by the collection. This method accepts a primary key or a model instance:
```cpp
users.contains(1);
users.contains(User::find(1));
```
Alternatively, you may pass a lambda expression to the `contains` method to determine if a model exists in the collection matching a given truth test:
```cpp
users.contains([](const User *const user)
{
return user->getKeyCasted() == 2;
});
```
For the inverse of `contains`, see the [doesntContain](#method-doesntcontain) method.
#### `doesntContain()` {#method-doesntcontain}
The `doesntContain` method determines whether the collection does not contain a given item. This method accepts a primary key or a model instance:
```cpp
users.doesntContain(1);
users.doesntContain(User::find(1));
```
Alternatively, you may pass a lambda expression to the `doesntContain` method to determine if a model does not exist in the collection matching a given truth test:
```cpp
users.doesntContain([](const User *const user)
{
return user->getKeyCasted() == 2;
});
```
For the inverse of `doesntContain`, see the [contains](#method-contains) method.
#### `each()` {#method-each}
The `each` method iterates over the models in the collection and passes each model to the lambda expression:
```cpp
ModelsCollection<User> users = Post::whereEq("user_id", 1)->get();
users.each([](User *const user)
{
// ...
});
```
If you would like to stop iterating through the models, you may return `false` from your lambda expression:
```cpp
users.each([](User *const user)
{
if (/* condition */)
return false;
// Some logic
return true;
});
```
You may also pass the lambda expression with two parameters, whereas the second one is an index:
```cpp
users.each([](User *const user, const std::size_t index)
{
// ...
});
```
The `each` method returns an lvalue __reference__ to the currently processed collection.
It can be also called on `ModelsCollection` rvalues, it returns an rvalue reference in this case.
#### `except()` {#method-except}
The `except` method returns all of the models that do not have the given primary keys:
```cpp
ModelsCollection<User *> usersResult = users.except({1, 2, 3});
```
All of the models are returned if the `ids` argument is empty `except({})`.
The order of models in the collection is preserved.
For the inverse of `except`, see the [only](#method-only) method.
#### `filter()` {#method-filter}
The `filter` method filters the collection using the lambda expression, keeping only those models that pass a given truth test:
```cpp
auto usersBanned = users.filter([](const User *const user)
{
return user->getAttribute<bool>("is_banned");
});
```
You may also pass the lambda expression with two parameters, whereas the second one is an index:
```cpp
auto usersBanned = users.filter([](const User *const user,
const std::size_t index)
{
return index < 10 && user->getAttribute<bool>("is_banned");
});
```
If no lambda expression is supplied, all models of the collection that are equivalent to the `nullptr` will be removed:
```cpp
ModelsCollection<User> usersRaw = User::findMany({1, 2});
ModelsCollection<User *> users {&usersRaw[0], nullptr, &usersRaw[1]};
ModelsCollection<User *> filtered = users.filter();
// {1, 2}
```
For the inverse of `filter`, see the [reject](#method-reject) method.
#### `find()` {#method-find}
The `find` method returns the model that has a primary key matching the given key:
```cpp
User *const user = users.find(1);
```
If you pass a model instance, `find` will attempt to return a model matching the primary key:
```cpp
User *user = users.find(anotherUser);
```
The two overloads above also accept the second `defaultModel` model argument, which will be returned if a model was not found in the collection, its default value is the `nullptr`.
Alternatively, may pass more IDs and `find` will return all models which have a primary key within the given unordered set:
```cpp
ModelsCollection<User *> usersMany = users.find({1, 2});
```
This overload internally calls the [`only`](#method-only) method.
#### `first()` {#method-first}
The `first` method returns the first model in the collection that passes a given truth test:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};
User *user = users.first([](User *const user)
{
return user->getAttribute<quint64>("votes") > 150;
});
// {{"name", "John"}, {"votes", 200}}
```
If no model passes a given truth test then the value of the second `defaultModel` argument will be returned, its default value is the `nullptr`.
```cpp
using NullVariant = Orm::Utils::NullVariant;
User defaultUser {{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}};
User *user = users.first([](User *const user)
{
return user->getAttribute<quint64>("votes") > 500;
},
&defaultUser);
/*
{{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}}
*/
```
You can also call all `first` overloads provided by the [`QList::first`](https://doc.qt.io/qt/qlist.html#first).
#### `firstWhere()` {#method-first-where}
The `firstWhere` method returns the first model in the collection with the given column / value pair:
```cpp
using NullVariant = Orm::Utils::NullVariant;
ModelsCollection<User> users {
{{"name", "Leon"}, {"age", NullVariant::UShort()}},
{{"name", "Jill"}, {"age", 14}},
{{"name", "Jack"}, {"age", 23}},
{{"name", "Jill"}, {"age", 84}},
};
auto user = users.firstWhereEq("name", "Linda");
// {{"name", "Jill"}, {"age", 14}}
```
You may also call the `firstWhere` method with a comparison operator:
```cpp
users.firstWhere("age", ">=", 18);
// {{"name", "Jack"}, {"age", 23}}
```
#### `fresh()` {#method-fresh}
The `fresh` method retrieves a fresh instance of each model in the collection from the database. In addition, any specified relationships will be eager loaded:
```cpp
auto usersFresh = users.fresh();
auto usersFresh = users.fresh("comments");
auto usersFresh = users.fresh("posts:id,name");
auto usersFresh = users.fresh({"comments", "posts:id,name"});
```
The `relations` argument format is the same as for TinyBuilder's [`load`](relationships.mdx#lazy-eager-loading) method.
#### `implode()` {#method-implode}
The `implode` method joins attributes by the given column and the "glue" string you wish to place between the values:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
};
products.implode("product", ", ");
// {Desk, Chair}
```
The default "glue" value is an empty string "".
#### `isEmpty()` {#method-isempty}
The `isEmpty` method returns `true` if the collection is empty; otherwise, `false` is returned:
```cpp
ModelsCollection<User>().isEmpty();
// true
```
#### `isNotEmpty()` {#method-isnotempty}
The `isNotEmpty` method returns `true` if the collection is not empty; otherwise, `false` is returned:
```cpp
ModelsCollection<User>().isNotEmpty();
// false
```
#### `last()` {#method-last}
The `last` method returns the last model in the collection that passes a given truth test:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
{{"name", "Rose"}, {"votes", 350}},
};
User *user = users.last([](User *const user)
{
return user->getAttribute<quint64>("votes") < 300;
});
// {{"name", "John"}, {"votes", 200}}
```
If no model passes a given truth test then the value of the second `defaultModel` argument will be returned, its default value is the `nullptr`.
```cpp
using NullVariant = Orm::Utils::NullVariant;
User defaultUser {{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}};
User *user = users.last([](User *const user)
{
return user->getAttribute<quint64>("votes") < 100;
},
&defaultUser);
/*
{{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}}
*/
```
You can also call all `last` overloads provided by the [`QList::last`](https://doc.qt.io/qt/qlist.html#last).
#### `load()` {#method-load}
The `load` method eager loads the given relationships for all models in the collection:
```cpp
users.load({"comments", "posts"});
users.load("comments.author");
users.load({{"comments"}, {"posts", [](auto &query)
{
query.whereEq("active", true);
}}});
```
The `relations` argument format is the same as for TinyBuilder's [`load`](relationships.mdx#lazy-eager-loading) method.
#### `map()` {#method-map}
The `map` method iterates through the collection and passes a __copy__ of each model to the given lambda expression. The lambda expression is free to modify the model and return it, thus forming a new collection of modified models:
```cpp
ModelsCollection<User> users {
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};
auto usersAdded = users.map([](User &&userCopy)
{
if (userCopy.getAttribute<QString>("name") == "John")
userCopy["votes"] = userCopy.getAttribute<quint64>("votes") + 1;
return std::move(userCopy);
});
/*
{
{{"name", "John"}, {"price", 201}},
{{"name", "Jack"}, {"price", 400}},
}
*/
```
The second `map` overload allows to return the `QList<T>`:
```cpp
QList<quint64> usersAdded = users.map<quint64>([](User &&userCopy)
{
const auto votesRef = userCopy["votes"];
if (userCopy.getAttribute<QString>("name") == "John")
votesRef = userCopy.getAttribute<quint64>("votes") + 1;
return votesRef->value<quint64>();
});
// {201, 400}
```
Both overloads allow to pass the lambda expression with two arguments, whereas the second argument can be an index of the `std::size_t` type.
:::caution
Like most other collection methods, `map` returns a new collection instance; it does not modify the collection it is called on. If you want to modify the original collection in place, use the [`each`](#method-each) method.
:::
:::info
The model copy is passed to the lambda expression even if the `map` iterates over a collection of model pointers `ModelsCollection<Model *>`. The models are dereferenced behind the scene.
:::
#### `mapWithKeys()` {#method-mapwithkeys}
The `mapWithKeys` method iterates through the collection and passes each model to the given lambda expression. It returns the `std::unordered_map<K, V>` and the lambda expression should return the `std::pair<K, V>` containing a single column / value pair:
```cpp
ModelsCollection<User> users {
{{"id", 1}, {"name", "John"}, {"email", "john@example.com"}},
{{"id", 2}, {"name", "Jill"}, {"email", "jill@example.com"}},
};
auto usersMap = users.mapWithKeys<quint64, QString>(
[](User *const user) -> std::pair<quint64, QString>
{
return {user->getKeyCasted(), user->getAttribute<QString>("name")};
});
// {{1, 'John'}, {2, 'Jill'}}
```
You can also map IDs to the model pointers:
```cpp
auto usersMap = users.mapWithKeys<quint64, User *>(
[](User *const user) -> std::pair<quint64, User *>
{
return {user->getKeyCasted(), user};
});
```
#### `mapWithModelKeys()` {#method-mapwithmodelkeys}
The `mapWithModelKeys` maps the primary keys to the `Model *`, it returns the `std::unordered_map<Model::KeyType, Model *>`:
```cpp
auto usersMap = users.mapWithModelKeys();
```
#### `modelKeys()` {#method-modelkeys}
The `modelKeys` method returns the primary keys for all models in the collection:
```cpp
ModelsCollection<User> users {
{{"id", 1}, {"name", "John"}},
{{"id", 2}, {"name", "Jill"}},
{{"id", 3}, {"name", "Kate"}},
{{"id", 5}, {"name", "Rose"}},
};
users.modelKeys(); // Returns QList<QVariant>
users.modelKeys<quint64>();
// {1, 2, 3, 5}
```
#### `only()` {#method-only}
The `only` method returns all of the models that have the given primary keys:
```cpp
ModelsCollection<User *> usersResult = users.only({1, 2, 3});
```
An empty collection is returned if the `ids` argument is empty `only({})`.
The order of models in the collection is preserved.
For the inverse of `only`, see the [except](#method-except) method.
#### `pluck()` {#method-pluck}
The `pluck` method retrieves all of the values for a given column, the following overload returns the `QList<QVariant>`:
```cpp
ModelsCollection<Product> products {
{{"id", 1}, {"name", "Desk"}},
{{"id", 2}, {"name", "Chair"}},
};
auto plucked = products.pluck("name");
// {Desk, Chair}
```
The second overload allows returning the custom type `QList<T>`:
```cpp
auto plucked = products.pluck<QString>("name");
```
You may also specify how you wish the resulting collection to be keyed, this overload returns the `std::map<T, QVariant>`:
```cpp
auto plucked = products.pluck<quint64>("name", "id");
// {{1, "Desk"}, {2, "Chair"}}
```
If duplicate keys exist, the last matching attribute will be inserted into the plucked collection:
```cpp
ModelsCollection<Product> collection {
{{"brand", "Tesla"}, {"color", "red"}},
{{"brand", "Pagani"}, {"color", "white"}},
{{"brand", "Tesla"}, {"color", "black"}},
{{"brand", "Pagani"}, {"color", "orange"}},
};
auto plucked = collection.pluck<QString>("color", "brand");
// {{'Tesla', 'black'}, {'Pagani', 'orange"}}
```
#### `reject()` {#method-reject}
The `reject` method filters the collection using the given lambda expression. The lambda should return `true` if the model should be removed from the resulting collection:
```cpp
auto usersWithNote = users.reject([](const User *const user)
{
return user->getAttribute("note").isNull();
});
```
You may also pass the lambda expression with two arguments, whereas the second argument can be an index of the `std::size_t` type.
For the inverse of the `reject` method, see the [`filter`](#method-filter) method.
#### `sort()` {#method-sort}
The `sort` method sorts the models collection by primary keys:
```cpp
ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};
auto sorted = users.sort();
/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/
```
You may pass a predicate and projection callbacks to the `sort` method with your own algorithms. Refer to the CPP reference documentation on [`ranges::sort`](https://en.cppreference.com/w/cpp/algorithm/ranges/sort), which is what the `sort` method calls internally.
You can eg. sort by multiple columns, for an alternative method of multi-column sorting look at [sortBy](#method-sortby):
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};
auto sorted = users.sort([](const User *const left,
const User *const right)
{
const auto leftValue = left->getAttribute<QString>("name");
const auto rightValue = right->getAttribute<QString>("name");
if (leftValue == rightValue)
return left->getAttribute<quint64>("votes") <
right->getAttribute<quint64>("votes");
return leftValue < rightValue;
});
/*
{
{{"name", "John"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 350}},
}
*/
```
The order of equal elements is not guaranteed to be preserved.
:::info
You can use the [stable](#method-stablesort) sort method variants to preserve the order of equal models.
:::
#### `sortBy()` {#method-sortby}
The `sortBy` method sorts the collection by the given column, this overload needs the template argument so it can cast the attribute value before comparing:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};
auto sorted = users.sortBy<QString>("name");
/*
{
{{"name", "Jack"}, {"votes", 400}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 150}},
}
*/
```
You may pass the projection callback to determine how to sort the collection's models:
```cpp
auto sorted = users.sortBy([](User *const user)
{
return user->getAttribute<quint64>("votes");
});
/*
{
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
}
*/
```
If you would like to sort your collection by multiple columns, you may pass a vector of comparison lambda expressions that define each sort operation to the `sortBy` method, in the following example is the `name` column sorted in ascending order and the second `votes` column is sorted in descending order:
```cpp
using AttributeUtils = Orm::Tiny::Utils::Attribute;
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};
auto sorted = users.sortBy({
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortBy(
left->getAttribute<QString>("name"),
right->getAttribute<QString>("name"));
},
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>("votes"),
right->getAttribute<quint64>("votes"));
},
});
/*
{
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 350}},
{{"name", "Kate"}, {"votes", 200}},
}
*/
```
The `AttributeUtils::compareForSortBy` and `compareForSortByDesc` methods are helper methods, they are needed because the Qt framework doesn't define `<=>` spaceship operator on its types, it doesn't support the three-way comparison.
The order of equal elements is not guaranteed to be preserved.
#### `sortByDesc()` {#method-sortbydesc}
This method has the same signature as the [`sortBy`](#method-sortby) method but will sort the collection in the opposite order.
The order of equal elements is not guaranteed to be preserved.
#### `sortDesc()` {#method-sortdesc}
This method will sort the collection in the opposite order as the [`sort`](#method-sort) method:
```cpp
ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};
auto sorted = users.sortDesc();
/*
{
{{"id", 3}, {"name", "John"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
}
*/
```
The order of equal elements is not guaranteed to be preserved.
#### `stableSort()` {#method-stablesort}
This method has the same signature as the [`sort`](#method-sort) method but will preserve the order of equal elements (guaranteed to be preserved).
#### `stableSortBy()` {#method-stablesortby}
This method has the same signature as the [`sortBy`](#method-sortby) method but will preserve the order of equal elements (guaranteed to be preserved).
#### `stableSortByDesc()` {#method-stablesortbydesc}
This method has the same signature as the [`sortByDesc`](#method-sortbydesc) method but will sort the collection in the opposite order and preserve the order of equal elements (guaranteed to be preserved).
#### `stableSortDesc()` {#method-stablesortdesc}
This method has the same signature as the [`sortDesc`](#method-sortdesc) method but will sort the collection in the opposite order and preserve the order of equal elements (guaranteed to be preserved).
#### `tap()` {#method-tap}
The `tap` method passes a collection to the given lambda expression, allowing you to "tap" into the collection at a specific point and do something with the models while not affecting the collection itself:
```cpp
ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};
users.sort()
.tap([](/*const */ModelsCollection<User *> &usersRef)
{
qDebug() << "IDs after sorting:"
<< usersRef.template modelKeys<quint64>();
})
.value<quint64>("id");
// 1
```
The `tap` method returns an lvalue __reference__ to the currently processed collection.
It can be also called on `ModelsCollection` rvalues, it returns an rvalue reference in this case.
#### `toBase()` {#method-tobase}
The `toBase` method returns a copy of the underlying vector represented by the collection:
```cpp
QList<User> = users.toBase();
```
:::note
The [`toBase`](#method-tobase) is an alias to the `all` method.
:::
#### `toJson()` {#method-tojson}
The `toJson` method converts the collection of models with all nested relations into a JSON serialized [`QByteArray`](https://doc.qt.io/qt/qbytearray.html).
It returns an empty array for empty `many` type relations and `null` for empty `one` type relations.
:::info
The `toJson` method accepts the [`QJsonDocument::JsonFormat`](https://doc.qt.io/qt/qjsondocument.html#JsonFormat-enum), possible values are `QJsonDocument::Indented` or `QJsonDocument::Compact`.
:::
#### `toJsonArray()` {#method-tojsonarray}
The `toJsonArray` method converts the collection of models with all nested relations into a [`QJsonArray`](https://doc.qt.io/qt/qjsonarray.html).
#### `toJsonDocument()` {#method-tojsondocument}
The `toJsonDocument` method converts the collection of models with all nested relations into a [`QJsonDocument`](https://doc.qt.io/qt/qjsondocument.html).
#### `toMap()` {#method-tomap}
The `toMap` method converts the collection of models with all nested relations into an attributes map `QList<QVariantMap>`.
It returns an empty `QVariantList` for empty `many` type relations and a null <abbr title='QVariant::fromValue(nullptr)'>`QVariant`</abbr> for empty `one` type relations.
#### `toMapVariantList()` {#method-tomapvariantlist}
The `toMapVariantList` method converts the collection of models with all nested relations into an attributes map, but it returns the <abbr title='QList<QVariant>'>`QVariantList`</abbr> instead of the `QList<QVariantMap>`.
It returns an empty `QVariantList` for empty `many` type relations and a null <abbr title='QVariant::fromValue(nullptr)'>`QVariant`</abbr> for empty `one` type relations.
:::note
The `toMapVariantList` method is internally needed by the `toJson` related methods.
:::
#### `toQuery()` {#method-toquery}
The `toQuery` method returns the `TinyBuilder` instance containing a `whereIn` constraint with the collection of models' primary keys:
```cpp
using Models::User;
ModelsCollection<User> users = User::whereEq("status", "VIP")->get();
users.toQuery()->update({
{"status", "Administrator"},
});
```
#### `toList()` {#method-tolist}
The `toList` method converts the collection of models with all nested relations into an attributes vector `QList<QList<AttributeItem>>`.
It returns an empty `QVariantList` for empty `many` type relations and a null <abbr title='QVariant::fromValue(nullptr)'>`QVariant`</abbr> for empty `one` type relations.
#### `toListVariantList()` {#method-tolistvariantlist}
The `toListVariantList` method converts the collection of models with all nested relations into an attributes vector, but it returns the <abbr title='QList<QVariant>'>`QVariantList`</abbr> instead of the `QList<QList<AttributeItem>>`.
It returns an empty `QVariantList` for empty `many` type relations and a null <abbr title='QVariant::fromValue(nullptr)'>`QVariant`</abbr> for empty `one` type relations.
:::note
The `toListVariantList` method is internally needed by the `toJson` related methods.
:::
#### `unique()` {#method-unique}
The `unique` method returns all of the unique models in the __sorted__ collection. Any models with the same primary key as another model in the collection are removed:
```cpp
ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};
auto unique = users.unique();
/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/
```
It sorts the collection internally because the [`ranges::unique`](https://en.cppreference.com/w/cpp/algorithm/ranges/unique) can correctly operate only on the sorted container. You can disable it by passing `false` using the first `sort` parameter:
```cpp
auto unique = users.sort().unique(false);
/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/
```
#### `uniqueBy()` {#method-uniqueby}
The `uniqueBy` method returns all of the unique models in the __sorted__ collection by the given column. Any models with the same column value as another model in the collection are removed. It needs the template argument, so it can cast the attribute value before comparing:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Jack"}},
};
auto unique = users.uniqueBy<QString>("name");
/*
{
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Kate"}},
}
*/
```
It sorts the collection internally because the [`ranges::unique`](https://en.cppreference.com/w/cpp/algorithm/ranges/unique) can correctly operate only on the sorted container. You can disable it by passing `false` using the second `sort` parameter:
```cpp
auto unique = users.sortBy<QString>("name")
.uniqueBy<QString>("name", false);
/*
{
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Kate"}},
}
*/
```
#### `uniqueRelaxed()` {#method-uniquerelaxed}
The `uniqueRelaxed` method returns all of the unique models in the collection, it doesn't need a sorted collection. Any models with the same primary key as another model in the collection are removed:
```cpp
ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};
auto unique = users.uniqueRelaxed();
/*
{
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
}
*/
```
#### `uniqueRelaxedBy()` {#method-uniquerelaxedby}
The `uniqueRelaxedBy` method returns all of the unique models in the collection by the given column, it doesn't need a sorted collection, but it needs the template argument, so it can cast the attribute value before comparing:
```cpp
ModelsCollection<User> users {
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Jack"}},
};
auto unique = users.uniqueRelaxedBy<QString>("name");
/*
{
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
}
*/
```
#### `value()` {#method-value}
The `value` method retrieves a given value from the first model of the collection:
```cpp
ModelsCollection<User> users {
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};
QVariant votes = users.value("votes");
// 200
```
Alternatively, you can cast an obtained `QVariant` value to the given type by the second `value` overload:
```cpp
quint64 votes = users.value<quint64>("votes");
```
The `value` method also accepts the second `defaultValue` argument, which will be returned if a collection is empty, the first model is `nullptr`, or a model doesn't contain the given column:
```cpp
auto votes = ModelsCollection<User>().value("votes", 0);
// 0
```
You can also call all `value` overloads provided by the [`QList::value`](https://doc.qt.io/qt/qlist.html#value).
#### `where()` {#method-where}
The `where` method filters the collection by a given column / value pair:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 100}},
};
auto filtered = products.where("price", "=", 100);
/*
{
{{"product", "Chair"}, {"price", 100}},
{{"product", "Door"}, {"price", 100}},
}
*/
```
For convenience, if you want to verify that a column is `=` to a given value, you may call `whereEq` method. Similar `XxxEq` methods are also defined for other commands:
auto filtered = products.whereEq("price", 100);
Optionally, you may pass a comparison operator as the second argument.<br/>Supported operators are `=`, `!=`, `<`, `>`, `<=`, and `>=`:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};
auto filtered = products.where("price", ">", 150);
/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Door"}, {"price", 250}},
}
*/
```
#### `whereBetween()` {#method-wherebetween}
The `whereBetween` method filters the collection by determining if a specified models' attribute value is within a given range:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 80}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Pencil"}, {"price", 30}},
{{"product", "Door"}, {"price", 100}},
};
auto filtered = products.whereBetween<quint64>("price", {100, 200});
/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 100}},
}
*/
```
#### `whereIn()` {#method-wherein}
The `whereIn` method filters models from the collection that have a specified attribute value that is contained within the given unordered set:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};
auto filtered = products.whereIn<quint64>("price", {100, 200});
/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
}
*/
```
An empty collection is returned if the `values` argument is empty `whereIn("price", {})`.
The order of models in the collection is preserved.
#### `whereNotBetween()` {#method-wherenotbetween}
The `whereNotBetween` method filters the collection by determining if a specified models' attribute value is outside of a given range:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 80}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Pencil"}, {"price", 30}},
{{"product", "Door"}, {"price", 100}},
};
auto filtered = products.whereNotBetween<quint64>("price", {100, 200});
/*
{
{{"product", "Chair"}, {"price", 80}},
{{"product", "Pencil"}, {"price", 30}},
}
*/
```
#### `whereNotIn()` {#method-wherenotin}
The `whereNotIn` method removes models from the collection that have a specified attribute value that is contained within the given unordered set:
```cpp
ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};
auto filtered = products.whereNotIn<quint64>("price", {100, 200});
/*
{
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
}
*/
```
All of the models are returned if the `values` argument is empty `whereNotIn("price", {})`.
The order of models in the collection is preserved.
#### `whereNotNull()` {#method-wherenotnull}
The `whereNotNull` method returns models from the collection where the given column is not `null` QVariant:
```cpp
#include <orm/utils/nullvariant.hpp>
using NullVariant = Orm::Utils::NullVariant;
ModelsCollection<User> users {
{{"name", "John"}},
{{"name", NullVariant::QString()}},
{{"name", "Jack"}},
};
auto filtered = users.whereNotNull("name");
/*
{
{{"name", "John"}},
{{"name", "Jack"}},
}
*/
```
:::note
The `NullVariant` class returns the correct `null` QVariant for both Qt 5 `QVariant(QVariant::String)` and also Qt 6 `QVariant(QMetaType(QMetaType::QString))`. The reason why this class still exists even after Qt v5.15 support was removed is the performance boost.
:::
#### `whereNull()` {#method-wherenull}
The `whereNull` method returns models from the collection where the given column is `null` QVariant:
```cpp
#include <orm/utils/nullvariant.hpp>
using NullVariant = Orm::Utils::NullVariant;
ModelsCollection<User> users {
{{"name", "John"}},
{{"name", NullVariant::QString()}},
{{"name", "Jack"}},
};
auto filtered = users.whereNotNull("name");
// {{"name", NullVariant::QString()}}
```
:::note
The `NullVariant` class returns the correct `null` QVariant for both Qt 5 `QVariant(QVariant::String)` and also Qt 6 `QVariant(QMetaType(QMetaType::QString))`. The reason why this class still exists even after Qt v5.15 support was removed is the performance boost.
:::
</div>